mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
merge mozilla-inbound to mozilla-central. r=merge a=merge
MozReview-Commit-ID: 790IXj5MZ4f
This commit is contained in:
commit
f9b5b9b40c
@ -175,23 +175,17 @@ HTMLTableCellAccessible::Table() const
|
||||
uint32_t
|
||||
HTMLTableCellAccessible::ColIdx() const
|
||||
{
|
||||
nsITableCellLayout* cellLayout = GetCellLayout();
|
||||
NS_ENSURE_TRUE(cellLayout, 0);
|
||||
|
||||
int32_t colIdx = 0;
|
||||
cellLayout->GetColIndex(colIdx);
|
||||
return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0;
|
||||
nsTableCellFrame* cellFrame = GetCellFrame();
|
||||
NS_ENSURE_TRUE(cellFrame, 0);
|
||||
return cellFrame->ColIndex();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
HTMLTableCellAccessible::RowIdx() const
|
||||
{
|
||||
nsITableCellLayout* cellLayout = GetCellLayout();
|
||||
NS_ENSURE_TRUE(cellLayout, 0);
|
||||
|
||||
int32_t rowIdx = 0;
|
||||
cellLayout->GetRowIndex(rowIdx);
|
||||
return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0;
|
||||
nsTableCellFrame* cellFrame = GetCellFrame();
|
||||
NS_ENSURE_TRUE(cellFrame, 0);
|
||||
return cellFrame->RowIndex();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
@ -285,6 +279,12 @@ HTMLTableCellAccessible::GetCellLayout() const
|
||||
return do_QueryFrame(mContent->GetPrimaryFrame());
|
||||
}
|
||||
|
||||
nsTableCellFrame*
|
||||
HTMLTableCellAccessible::GetCellFrame() const
|
||||
{
|
||||
return do_QueryFrame(mContent->GetPrimaryFrame());
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const
|
||||
{
|
||||
@ -520,11 +520,9 @@ HTMLTableAccessible::SelectedCellCount()
|
||||
if (!cellFrame || !cellFrame->IsSelected())
|
||||
continue;
|
||||
|
||||
int32_t startRow = -1, startCol = -1;
|
||||
cellFrame->GetRowIndex(startRow);
|
||||
cellFrame->GetColIndex(startCol);
|
||||
if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
|
||||
startCol >= 0 && (uint32_t)startCol == colIdx)
|
||||
uint32_t startRow = cellFrame->RowIndex();
|
||||
uint32_t startCol = cellFrame->ColIndex();
|
||||
if (startRow == rowIdx && startCol == colIdx)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@ -570,11 +568,9 @@ HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
|
||||
if (!cellFrame || !cellFrame->IsSelected())
|
||||
continue;
|
||||
|
||||
int32_t startCol = -1, startRow = -1;
|
||||
cellFrame->GetRowIndex(startRow);
|
||||
cellFrame->GetColIndex(startCol);
|
||||
if ((startRow >= 0 && (uint32_t)startRow != rowIdx) ||
|
||||
(startCol >= 0 && (uint32_t)startCol != colIdx))
|
||||
uint32_t startRow = cellFrame->RowIndex();
|
||||
uint32_t startCol = cellFrame->ColIndex();
|
||||
if (startRow != rowIdx || startCol != colIdx)
|
||||
continue;
|
||||
|
||||
Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
|
||||
@ -597,11 +593,9 @@ HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
|
||||
if (!cellFrame || !cellFrame->IsSelected())
|
||||
continue;
|
||||
|
||||
int32_t startRow = -1, startCol = -1;
|
||||
cellFrame->GetColIndex(startCol);
|
||||
cellFrame->GetRowIndex(startRow);
|
||||
if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
|
||||
startCol >= 0 && (uint32_t)startCol == colIdx)
|
||||
uint32_t startCol = cellFrame->ColIndex();
|
||||
uint32_t startRow = cellFrame->RowIndex();
|
||||
if (startRow == rowIdx && startCol == colIdx)
|
||||
aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "TableCellAccessible.h"
|
||||
|
||||
class nsITableCellLayout;
|
||||
class nsTableCellFrame;
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
@ -53,6 +54,11 @@ protected:
|
||||
*/
|
||||
nsITableCellLayout* GetCellLayout() const;
|
||||
|
||||
/**
|
||||
* Return the table cell frame.
|
||||
*/
|
||||
nsTableCellFrame* GetCellFrame() const;
|
||||
|
||||
/**
|
||||
* Return row and column indices of the cell.
|
||||
*/
|
||||
|
@ -108,6 +108,7 @@ function CustomizeMode(aWindow) {
|
||||
// to the user when in customizing mode.
|
||||
this.visiblePalette = this.document.getElementById(kPaletteId);
|
||||
this.paletteEmptyNotice = this.document.getElementById("customization-empty");
|
||||
this.pongArena = this.document.getElementById("customization-pong-arena");
|
||||
if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
|
||||
let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
|
||||
lwthemeButton.setAttribute("hidden", "true");
|
||||
@ -408,9 +409,7 @@ CustomizeMode.prototype = {
|
||||
let window = this.window;
|
||||
let document = this.document;
|
||||
|
||||
// Hide the palette before starting the transition for increased perf.
|
||||
this.visiblePalette.hidden = true;
|
||||
this.visiblePalette.removeAttribute("showing");
|
||||
this.togglePong(false);
|
||||
this.paletteEmptyNotice.hidden = true;
|
||||
|
||||
// Disable the reset and undo reset buttons while transitioning:
|
||||
@ -1538,6 +1537,15 @@ CustomizeMode.prototype = {
|
||||
_updateEmptyPaletteNotice() {
|
||||
let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
|
||||
this.paletteEmptyNotice.hidden = !!paletteItems.length;
|
||||
let readyPlayerOne = this.document.getElementById("ready-player-one");
|
||||
|
||||
if (paletteItems.length == 1 &&
|
||||
paletteItems[0].id.includes("wrapper-customizableui-special-spring")) {
|
||||
readyPlayerOne.hidden = false;
|
||||
} else {
|
||||
this.togglePong(false);
|
||||
readyPlayerOne.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
_updateResetButton() {
|
||||
@ -2520,6 +2528,215 @@ CustomizeMode.prototype = {
|
||||
// Ensure we move the button (back) after the user leaves customize mode.
|
||||
event.view.gCustomizeMode._moveDownloadsButtonToNavBar = checkbox.checked;
|
||||
},
|
||||
|
||||
togglePong(enabled) {
|
||||
// It's possible we're toggling for a reason other than hitting
|
||||
// the button (we might be exiting, for example), so make sure that
|
||||
// the state and checkbox are in sync.
|
||||
let readyPlayerOne = this.document.getElementById("ready-player-one");
|
||||
readyPlayerOne.checked = enabled;
|
||||
|
||||
if (enabled) {
|
||||
this.visiblePalette.setAttribute("whimsypong", "true");
|
||||
this.pongArena.hidden = false;
|
||||
if (!this.uninitWhimsy) {
|
||||
this.uninitWhimsy = this.whimsypong();
|
||||
}
|
||||
} else {
|
||||
this.visiblePalette.removeAttribute("whimsypong");
|
||||
if (this.uninitWhimsy) {
|
||||
this.uninitWhimsy();
|
||||
this.uninitWhimsy = null;
|
||||
}
|
||||
this.pongArena.hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
whimsypong() {
|
||||
function update() {
|
||||
updateBall();
|
||||
updatePlayers();
|
||||
}
|
||||
|
||||
function updateBall() {
|
||||
if (ball[1] <= 0 || ball[1] >= gameSide) {
|
||||
if ((ball[1] <= 0 && (ball[0] < p1 || ball[0] > p1 + paddleWidth)) ||
|
||||
(ball[1] >= gameSide && (ball[0] < p2 || ball[0] > p2 + paddleWidth))) {
|
||||
updateScore(ball[1] <= 0 ? 0 : 1);
|
||||
} else {
|
||||
if ((ball[1] <= 0 && (ball[0] - p1 < paddleEdge || p1 + paddleWidth - ball[0] < paddleEdge)) ||
|
||||
(ball[1] >= gameSide && (ball[0] - p2 < paddleEdge || p2 + paddleWidth - ball[0] < paddleEdge))) {
|
||||
ballDxDy[0] *= Math.random() + 1.3;
|
||||
ballDxDy[0] = Math.max(Math.min(ballDxDy[0], 6), -6);
|
||||
if (Math.abs(ballDxDy[0]) == 6) {
|
||||
ballDxDy[0] += Math.sign(ballDxDy[0]) * Math.random();
|
||||
}
|
||||
} else {
|
||||
ballDxDy[0] /= 1.1;
|
||||
}
|
||||
ballDxDy[1] *= -1;
|
||||
ball[1] = ball[1] <= 0 ? 0 : gameSide;
|
||||
}
|
||||
}
|
||||
ball = [Math.max(Math.min(ball[0] + ballDxDy[0], gameSide), 0),
|
||||
Math.max(Math.min(ball[1] + ballDxDy[1], gameSide), 0)];
|
||||
if (ball[0] <= 0 || ball[0] >= gameSide) {
|
||||
ballDxDy[0] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePlayers() {
|
||||
if (keydown) {
|
||||
p1 += (keydown == 37 ? -1 : 1) * 10 * keydownAdj;
|
||||
}
|
||||
|
||||
let sign = Math.sign(ballDxDy[0]);
|
||||
if ((sign > 0 && ball[0] > p2 + paddleWidth / 2) ||
|
||||
(sign < 0 && ball[0] < p2 + paddleWidth / 2)) {
|
||||
p2 += sign * 3;
|
||||
} else if ((sign > 0 && ball[0] > p2 + paddleWidth / 1.1) ||
|
||||
(sign < 0 && ball[0] < p2 + paddleWidth / 1.1)) {
|
||||
p2 += sign * 9;
|
||||
}
|
||||
|
||||
if (score >= winScore) {
|
||||
p1 = ball[0];
|
||||
p2 = ball[0];
|
||||
}
|
||||
p1 = Math.max(Math.min(p1, gameSide - paddleWidth), 0);
|
||||
p2 = Math.max(Math.min(p2, gameSide - paddleWidth), 0);
|
||||
}
|
||||
|
||||
function updateScore(adj) {
|
||||
if (adj) {
|
||||
score += adj;
|
||||
} else if (--lives == 0) {
|
||||
quit = true;
|
||||
}
|
||||
ball = ballDef.slice();
|
||||
ballDxDy = ballDxDyDef.slice();
|
||||
ballDxDy[1] *= score / winScore + 1;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
elements.player1.style.transform = "translate(" + p1 + "px, -37px)";
|
||||
elements.player2.style.transform = "translate(" + p2 + "px, 300px)";
|
||||
elements.ball.style.transform = "translate(" + ball[0] + "px, " + ball[1] + "px)";
|
||||
elements.score.textContent = score;
|
||||
elements.lives.setAttribute("lives", lives);
|
||||
if (score >= winScore) {
|
||||
let arena = elements.arena;
|
||||
let image = "url(chrome://browser/skin/customizableui/whimsy.png)";
|
||||
let position = `${ball[0] - 10}px ${ball[1] - 10}px`;
|
||||
let repeat = "no-repeat";
|
||||
let size = "20px";
|
||||
if (arena.style.backgroundImage) {
|
||||
if (arena.style.backgroundImage.split(",").length >= 160) {
|
||||
quit = true;
|
||||
}
|
||||
|
||||
image += ", " + arena.style.backgroundImage;
|
||||
position += ", " + arena.style.backgroundPosition;
|
||||
repeat += ", " + arena.style.backgroundRepeat;
|
||||
size += ", " + arena.style.backgroundSize;
|
||||
}
|
||||
arena.style.backgroundImage = image;
|
||||
arena.style.backgroundPosition = position;
|
||||
arena.style.backgroundRepeat = repeat;
|
||||
arena.style.backgroundSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
function onkeydown(event) {
|
||||
if (event.which == 37 /* left */ ||
|
||||
event.which == 39 /* right */) {
|
||||
keydown = event.which;
|
||||
keydownAdj *= 1.05;
|
||||
}
|
||||
}
|
||||
|
||||
function onkeyup(event) {
|
||||
if (event.which == 37 || event.which == 39) {
|
||||
keydownAdj = 1;
|
||||
keydown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function uninit() {
|
||||
document.removeEventListener("keydown", onkeydown);
|
||||
document.removeEventListener("keyup", onkeyup);
|
||||
if (rAFHandle) {
|
||||
window.cancelAnimationFrame(rAFHandle);
|
||||
}
|
||||
let arena = elements.arena;
|
||||
while (arena.firstChild) {
|
||||
arena.firstChild.remove();
|
||||
}
|
||||
arena.removeAttribute("score");
|
||||
arena.style.removeProperty("background-image");
|
||||
arena.style.removeProperty("background-position");
|
||||
arena.style.removeProperty("background-repeat");
|
||||
arena.style.removeProperty("background-size");
|
||||
elements = null;
|
||||
document = null;
|
||||
quit = true;
|
||||
}
|
||||
|
||||
if (this.uninitWhimsy) {
|
||||
return this.uninitWhimsy;
|
||||
}
|
||||
|
||||
let ballDef = [10, 10];
|
||||
let ball = [10, 10];
|
||||
let ballDxDyDef = [2, 2];
|
||||
let ballDxDy = [2, 2];
|
||||
let score = 0;
|
||||
let p1 = 0;
|
||||
let p2 = 10;
|
||||
let gameSide = 300;
|
||||
let paddleEdge = 30;
|
||||
let paddleWidth = 84;
|
||||
let keydownAdj = 1;
|
||||
let keydown = 0;
|
||||
let lives = 5;
|
||||
let winScore = 11;
|
||||
let quit = false;
|
||||
let document = this.document;
|
||||
let rAFHandle = 0;
|
||||
let elements = {
|
||||
arena: document.getElementById("customization-pong-arena")
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", onkeydown);
|
||||
document.addEventListener("keyup", onkeyup);
|
||||
|
||||
for (let id of ["player1", "player2", "ball", "score", "lives"]) {
|
||||
let el = document.createElement("box");
|
||||
el.id = id;
|
||||
elements[id] = elements.arena.appendChild(el);
|
||||
}
|
||||
|
||||
let spacer = this.visiblePalette.querySelector("toolbarpaletteitem");
|
||||
for (let player of ["#player1", "#player2"]) {
|
||||
let val = "-moz-element(#" + spacer.id + ") no-repeat";
|
||||
elements.arena.querySelector(player).style.background = val;
|
||||
}
|
||||
|
||||
let window = this.window;
|
||||
rAFHandle = window.requestAnimationFrame(function animate() {
|
||||
update();
|
||||
draw();
|
||||
if (quit) {
|
||||
elements.score.textContent = score;
|
||||
elements.lives && elements.lives.setAttribute("lives", lives);
|
||||
elements.arena.setAttribute("score", score);
|
||||
} else {
|
||||
rAFHandle = window.requestAnimationFrame(animate);
|
||||
}
|
||||
});
|
||||
|
||||
return uninit;
|
||||
},
|
||||
};
|
||||
|
||||
function __dumpDragData(aEvent, caller) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
</label>
|
||||
</hbox>
|
||||
<vbox id="customization-palette" class="customization-palette" hidden="true"/>
|
||||
<vbox id="customization-pong-arena" hidden="true"/>
|
||||
<spacer id="customization-spacer"/>
|
||||
</box>
|
||||
<vbox id="customization-panel-container">
|
||||
@ -127,6 +128,12 @@
|
||||
</panel>
|
||||
</button>
|
||||
|
||||
<button id="ready-player-one"
|
||||
type="checkbox"
|
||||
class="customizationmode-button"
|
||||
oncommand="gCustomizeMode.togglePong(this.checked);"
|
||||
hidden="true"/>
|
||||
|
||||
<spacer id="customization-footer-spacer"/>
|
||||
<button id="customization-undo-reset-button"
|
||||
class="customizationmode-button"
|
||||
|
@ -45,7 +45,7 @@ const OBSERVING = [
|
||||
"quit-application-granted", "browser-lastwindow-close-granted",
|
||||
"quit-application", "browser:purge-session-history",
|
||||
"browser:purge-domain-data",
|
||||
"idle-daily",
|
||||
"idle-daily", "clear-origin-attributes-data"
|
||||
];
|
||||
|
||||
// XUL Window properties to (re)store
|
||||
@ -799,6 +799,13 @@ var SessionStoreInternal = {
|
||||
this.onIdleDaily();
|
||||
this._notifyOfClosedObjectsChange();
|
||||
break;
|
||||
case "clear-origin-attributes-data":
|
||||
let userContextId = 0;
|
||||
try {
|
||||
userContextId = JSON.parse(aData).userContextId;
|
||||
} catch (e) {}
|
||||
if (userContextId)
|
||||
this._forgetTabsWithUserContextId(userContextId);
|
||||
}
|
||||
},
|
||||
|
||||
@ -2717,6 +2724,33 @@ var SessionStoreInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
// This method deletes all the closedTabs matching userContextId.
|
||||
_forgetTabsWithUserContextId(userContextId) {
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let window = windowsEnum.getNext();
|
||||
let windowState = this._windows[window.__SSi];
|
||||
if (windowState) {
|
||||
// In order to remove the tabs in the correct order, we store the
|
||||
// indexes, into an array, then we revert the array and remove closed
|
||||
// data from the last one going backward.
|
||||
let indexes = [];
|
||||
windowState._closedTabs.forEach((closedTab, index) => {
|
||||
if (closedTab.state.userContextId == userContextId) {
|
||||
indexes.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
for (let index of indexes.reverse()) {
|
||||
this.removeClosedTabData(windowState._closedTabs, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify of changes to closed objects.
|
||||
this._notifyOfClosedObjectsChange();
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores the session state stored in LastSession. This will attempt
|
||||
* to merge data into the current session. If a window was opened at startup
|
||||
|
@ -121,7 +121,8 @@
|
||||
}
|
||||
|
||||
.customizationmode-button:-moz-any(:focus,:active,:hover):not([disabled]),
|
||||
.customizationmode-button[open] {
|
||||
.customizationmode-button[open],
|
||||
.customizationmode-button[checked] {
|
||||
background-color: #e1e1e5;
|
||||
}
|
||||
|
||||
@ -557,3 +558,89 @@ toolbarpaletteitem[place=toolbar] > toolbarspring {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#ready-player-one {
|
||||
/* Don't need HiDPI versions since the size used will be scaled down to 20x20. */
|
||||
list-style-image: url("chrome://browser/skin/customizableui/whimsy.png");
|
||||
}
|
||||
|
||||
#ready-player-one > .button-box > .button-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#customization-palette[whimsypong] {
|
||||
/* Keep the palette in the render tree but invisible
|
||||
so -moz-element() will work. */
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
#customization-palette[whimsypong] > toolbarpaletteitem > toolbarspring {
|
||||
margin: 0 -7px;
|
||||
}
|
||||
|
||||
#lives,
|
||||
#ball {
|
||||
/* Don't need HiDPI versions since the size used will be scaled down to 20x20. */
|
||||
background-image: url("chrome://browser/skin/customizableui/whimsy.png");
|
||||
background-size: contain;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#customization-pong-arena {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-left: 1px solid currentColor;
|
||||
border-right: 1px solid currentColor;
|
||||
margin: 16px auto 0;
|
||||
}
|
||||
|
||||
#ball {
|
||||
margin-left: -10px;
|
||||
margin-top: -10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#player1,
|
||||
#player2 {
|
||||
width: 84px;
|
||||
height: calc(39px + 3em);
|
||||
background-color: rgba(255,255,0,.5);
|
||||
}
|
||||
|
||||
#player1,
|
||||
#player2,
|
||||
#ball,
|
||||
#score {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#score {
|
||||
transform: translateX(-4ch);
|
||||
}
|
||||
|
||||
#lives {
|
||||
transform: translate(-4ch, 1ch);
|
||||
}
|
||||
|
||||
#lives[lives="5"] {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
#lives[lives="4"] {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#lives[lives="3"] {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
#lives[lives="2"] {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#lives[lives="1"] {
|
||||
height: 20px;
|
||||
}
|
||||
|
12
config/external/ffi/Makefile.in
vendored
12
config/external/ffi/Makefile.in
vendored
@ -1,12 +0,0 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
# libffi's assembly files want to be pre-processed, so we still use the libffi
|
||||
# wrapper to combine the preprocessor and assembler stages.
|
||||
# Bug 1299959 is on file to find a better way to do this in moz.build.
|
||||
ifdef _MSC_VER
|
||||
AS = $(topsrcdir)/js/src/ctypes/libffi/msvcc.sh
|
||||
endif
|
25
config/external/ffi/moz.build
vendored
25
config/external/ffi/moz.build
vendored
@ -85,13 +85,32 @@ else:
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_64':
|
||||
ffi_srcs = ('ffi64.c', 'unix64.S', 'ffi.c', 'sysv.S')
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_WIN32':
|
||||
ffi_srcs = ['ffi.c']
|
||||
# MinGW Build for 32 bit
|
||||
if CONFIG['CC_TYPE'] == 'gcc':
|
||||
DEFINES['SYMBOL_UNDERSCORE'] = True
|
||||
ffi_srcs = ('ffi.c', 'win32.S')
|
||||
ffi_srcs += ['win32.S']
|
||||
else:
|
||||
# libffi asm needs to be preprocessed for MSVC
|
||||
GENERATED_FILES += ['win32.asm']
|
||||
asm = GENERATED_FILES['win32.asm']
|
||||
asm.inputs = ['/js/src/ctypes/libffi/src/x86/win32.S']
|
||||
asm.script = 'preprocess_libffi_asm.py'
|
||||
asm.flags = ['$(DEFINES)', '$(LOCAL_INCLUDES)']
|
||||
SOURCES += ['!win32.asm']
|
||||
ASFLAGS += ['-safeseh']
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_WIN64':
|
||||
ffi_srcs = ('ffi.c', 'win64.S')
|
||||
ASFLAGS += ['-m64']
|
||||
ffi_srcs = ['ffi.c']
|
||||
if CONFIG['CC_TYPE'] == 'gcc':
|
||||
ffi_srcs += ['win64.S']
|
||||
else:
|
||||
# libffi asm needs to be preprocessed for MSVC
|
||||
GENERATED_FILES += ['win64.asm']
|
||||
asm = GENERATED_FILES['win64.asm']
|
||||
asm.inputs = ['/js/src/ctypes/libffi/src/x86/win64.S']
|
||||
asm.script = 'preprocess_libffi_asm.py'
|
||||
asm.flags = ['$(DEFINES)', '$(LOCAL_INCLUDES)']
|
||||
SOURCES += ['!win64.asm']
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_DARWIN':
|
||||
DEFINES['FFI_MMAP_EXEC_WRIT'] = True
|
||||
if CONFIG['OS_TEST'] != 'x86_64':
|
||||
|
26
config/external/ffi/preprocess_libffi_asm.py
vendored
Normal file
26
config/external/ffi/preprocess_libffi_asm.py
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Souce Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distibuted with this
|
||||
# file, You can obtain one at http://mozilla.og/MPL/2.0/.
|
||||
|
||||
import buildconfig
|
||||
import mozpack.path as mozpath
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
def main(output, input_asm, defines, includes):
|
||||
defines = shlex.split(defines)
|
||||
includes = shlex.split(includes)
|
||||
# CPP uses -E which generates #line directives. -EP suppresses them.
|
||||
cpp = buildconfig.substs['CPP'] + ['-EP']
|
||||
input_asm = mozpath.relpath(input_asm, os.getcwd())
|
||||
args = cpp + defines + includes + [input_asm]
|
||||
print(' '.join(args))
|
||||
preprocessed = subprocess.check_output(args)
|
||||
r = re.compile('F[dpa][^ ]*')
|
||||
for line in preprocessed.splitlines():
|
||||
output.write(r.sub('', line))
|
||||
output.write('\n')
|
102
dom/cache/DBSchema.cpp
vendored
102
dom/cache/DBSchema.cpp
vendored
@ -35,8 +35,44 @@ namespace cache {
|
||||
namespace db {
|
||||
const int32_t kFirstShippedSchemaVersion = 15;
|
||||
namespace {
|
||||
// ## Firefox 57 Cache API v25/v26/v27 Schema Hack Info
|
||||
// ### Overview
|
||||
// In Firefox 57 we introduced Cache API schema version 26 and Quota Manager
|
||||
// schema v3 to support tracking padding for opaque responses. Unfortunately,
|
||||
// Firefox 57 is a big release that may potentially result in users downgrading
|
||||
// to Firefox 56 due to 57 retiring add-ons. These schema changes have the
|
||||
// unfortunate side-effect of causing QuotaManager and all its clients to break
|
||||
// if the user downgrades to 56. In order to avoid making a bad situation
|
||||
// worse, we're now retrofitting 57 so that Firefox 56 won't freak out.
|
||||
//
|
||||
// ### Implementation
|
||||
// We're introducing a new schema version 27 that uses an on-disk schema version
|
||||
// of v25. We differentiate v25 from v27 by the presence of the column added
|
||||
// by v26. This translates to:
|
||||
// - v25: on-disk schema=25, no "response_padding_size" column in table
|
||||
// "entries".
|
||||
// - v26: on-disk schema=26, yes "response_padding_size" column in table
|
||||
// "entries".
|
||||
// - v27: on-disk schema=25, yes "response_padding_size" column in table
|
||||
// "entries".
|
||||
//
|
||||
// ### Fallout
|
||||
// Firefox 57 is happy because it sees schema 27 and everything is as it
|
||||
// expects.
|
||||
//
|
||||
// Firefox 56 non-DEBUG build is fine/happy, but DEBUG builds will not be.
|
||||
// - Our QuotaClient will invoke `NS_WARNING("Unknown Cache file found!");`
|
||||
// at QuotaManager init time. This is harmless but annoying and potentially
|
||||
// misleading.
|
||||
// - The DEBUG-only Validate() call will error out whenever an attempt is made
|
||||
// to open a DOM Cache database because it will notice the schema is broken
|
||||
// and there is no attempt at recovery.
|
||||
//
|
||||
const int32_t kHackyDowngradeSchemaVersion = 25;
|
||||
const int32_t kHackyPaddingSizePresentVersion = 27;
|
||||
//
|
||||
// Update this whenever the DB schema is changed.
|
||||
const int32_t kLatestSchemaVersion = 26;
|
||||
const int32_t kLatestSchemaVersion = 27;
|
||||
// ---------
|
||||
// The following constants define the SQL schema. These are defined in the
|
||||
// same order the SQL should be executed in CreateOrMigrateSchema(). They are
|
||||
@ -356,6 +392,8 @@ static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
|
||||
mozIStorageStatement** aStateOut);
|
||||
static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
|
||||
nsACString& aOut);
|
||||
nsresult GetEffectiveSchemaVersion(mozIStorageConnection* aConn,
|
||||
int32_t& schemaVersion);
|
||||
nsresult Validate(mozIStorageConnection* aConn);
|
||||
nsresult Migrate(mozIStorageConnection* aConn);
|
||||
} // namespace
|
||||
@ -412,7 +450,7 @@ CreateOrMigrateSchema(mozIStorageConnection* aConn)
|
||||
MOZ_DIAGNOSTIC_ASSERT(aConn);
|
||||
|
||||
int32_t schemaVersion;
|
||||
nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
|
||||
nsresult rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
if (schemaVersion == kLatestSchemaVersion) {
|
||||
@ -473,10 +511,10 @@ CreateOrMigrateSchema(mozIStorageConnection* aConn)
|
||||
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
|
||||
rv = aConn->SetSchemaVersion(kHackyDowngradeSchemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
rv = aConn->GetSchemaVersion(&schemaVersion);
|
||||
rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
}
|
||||
|
||||
@ -2463,6 +2501,44 @@ IncrementalVacuum(mozIStorageConnection* aConn)
|
||||
|
||||
namespace {
|
||||
|
||||
// Wrapper around mozIStorageConnection::GetSchemaVersion() that compensates
|
||||
// for hacky downgrade schema version tricks. See the block comments for
|
||||
// kHackyDowngradeSchemaVersion and kHackyPaddingSizePresentVersion.
|
||||
nsresult
|
||||
GetEffectiveSchemaVersion(mozIStorageConnection* aConn,
|
||||
int32_t& schemaVersion)
|
||||
{
|
||||
nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
if (schemaVersion == kHackyDowngradeSchemaVersion) {
|
||||
// This is the special case. Check for the existence of the
|
||||
// "response_padding_size" colum in table "entries".
|
||||
//
|
||||
// (pragma_table_info is a table-valued function format variant of
|
||||
// "PRAGMA table_info" supported since SQLite 3.16.0. Firefox 53 shipped
|
||||
// was the first release with this functionality, shipping 3.16.2.)
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT name FROM pragma_table_info('entries') WHERE "
|
||||
"name = 'response_padding_size'"
|
||||
), getter_AddRefs(stmt));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
// If there are any result rows, then the column is present.
|
||||
bool hasColumn = false;
|
||||
rv = stmt->ExecuteStep(&hasColumn);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
if (hasColumn) {
|
||||
schemaVersion = kHackyPaddingSizePresentVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
struct Expect
|
||||
{
|
||||
@ -2492,7 +2568,7 @@ nsresult
|
||||
Validate(mozIStorageConnection* aConn)
|
||||
{
|
||||
int32_t schemaVersion;
|
||||
nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
|
||||
nsresult rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) {
|
||||
@ -2598,6 +2674,7 @@ nsresult MigrateFrom22To23(mozIStorageConnection* aConn, bool& aRewriteSchema);
|
||||
nsresult MigrateFrom23To24(mozIStorageConnection* aConn, bool& aRewriteSchema);
|
||||
nsresult MigrateFrom24To25(mozIStorageConnection* aConn, bool& aRewriteSchema);
|
||||
nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema);
|
||||
nsresult MigrateFrom26To27(mozIStorageConnection* aConn, bool& aRewriteSchema);
|
||||
// Configure migration functions to run for the given starting version.
|
||||
Migration sMigrationList[] = {
|
||||
Migration(15, MigrateFrom15To16),
|
||||
@ -2611,6 +2688,7 @@ Migration sMigrationList[] = {
|
||||
Migration(23, MigrateFrom23To24),
|
||||
Migration(24, MigrateFrom24To25),
|
||||
Migration(25, MigrateFrom25To26),
|
||||
Migration(26, MigrateFrom26To27),
|
||||
};
|
||||
uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
|
||||
nsresult
|
||||
@ -2649,7 +2727,7 @@ Migrate(mozIStorageConnection* aConn)
|
||||
MOZ_DIAGNOSTIC_ASSERT(aConn);
|
||||
|
||||
int32_t currentVersion = 0;
|
||||
nsresult rv = aConn->GetSchemaVersion(¤tVersion);
|
||||
nsresult rv = GetEffectiveSchemaVersion(aConn, currentVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
|
||||
bool rewriteSchema = false;
|
||||
@ -2675,7 +2753,7 @@ Migrate(mozIStorageConnection* aConn)
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
int32_t lastVersion = currentVersion;
|
||||
#endif
|
||||
rv = aConn->GetSchemaVersion(¤tVersion);
|
||||
rv = GetEffectiveSchemaVersion(aConn, currentVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
MOZ_DIAGNOSTIC_ASSERT(currentVersion > lastVersion);
|
||||
}
|
||||
@ -3174,6 +3252,16 @@ nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema)
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult MigrateFrom26To27(mozIStorageConnection* aConn, bool& aRewriteSchema)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_DIAGNOSTIC_ASSERT(aConn);
|
||||
|
||||
nsresult rv = aConn->SetSchemaVersion(kHackyDowngradeSchemaVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // namespace db
|
||||
} // namespace cache
|
||||
|
2
dom/cache/QuotaClient.cpp
vendored
2
dom/cache/QuotaClient.cpp
vendored
@ -301,7 +301,7 @@ public:
|
||||
}
|
||||
|
||||
nsresult
|
||||
UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory) override
|
||||
UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory) override
|
||||
{
|
||||
AssertIsOnIOThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(aDirectory);
|
||||
|
293
dom/file/tests/common_blob.js
Normal file
293
dom/file/tests/common_blob.js
Normal file
@ -0,0 +1,293 @@
|
||||
const RANGE_1 = 1;
|
||||
const RANGE_2 = 2;
|
||||
|
||||
function testBlob(file, contents, testName) {
|
||||
// Load file using FileReader
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"[FileReader] readyState in test FileReader.readAsBinaryString of " + testName);
|
||||
is(event.target.error, null,
|
||||
"[FileReader] no error in test FileReader.readAsBinaryString of " + testName);
|
||||
// Do not use |is(event.target.result, contents, "...");| that may output raw binary data.
|
||||
is(event.target.result.length, contents.length,
|
||||
"[FileReader] Length of result in test FileReader.readAsBinaryString of " + testName);
|
||||
ok(event.target.result == contents,
|
||||
"[FileReader] Content of result in test FileReader.readAsBinaryString of " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"[FileReader] lengthComputable in test FileReader.readAsBinaryString of " + testName);
|
||||
is(event.loaded, contents.length,
|
||||
"[FileReader] Loaded length in test FileReader.readAsBinaryString of " + testName);
|
||||
is(event.total, contents.length,
|
||||
"[FileReader] Total length in test FileReader.readAsBinaryString of " + testName);
|
||||
resolve();
|
||||
}
|
||||
r.readAsBinaryString(file);
|
||||
})
|
||||
|
||||
// Load file using URL.createObjectURL and XMLHttpRequest
|
||||
.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", URL.createObjectURL(file));
|
||||
xhr.onload = event => {
|
||||
XHRLoadHandler(event, resolve, contents, "XMLHttpRequest load of " + testName);
|
||||
};
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
xhr.send();
|
||||
});
|
||||
})
|
||||
|
||||
// Send file to server using FormData and XMLHttpRequest
|
||||
.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onload = function(event) {
|
||||
checkMPSubmission(JSON.parse(event.target.responseText),
|
||||
[{ name: "hello", value: "world"},
|
||||
{ name: "myfile",
|
||||
value: contents,
|
||||
fileName: file.name || "blob",
|
||||
contentType: file.type || "application/octet-stream" }]);
|
||||
resolve();
|
||||
}
|
||||
xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
|
||||
|
||||
let fd = new FormData;
|
||||
fd.append("hello", "world");
|
||||
fd.append("myfile", file);
|
||||
|
||||
xhr.send(fd);
|
||||
});
|
||||
})
|
||||
|
||||
// Send file to server using plain XMLHttpRequest
|
||||
.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs");
|
||||
|
||||
xhr.onload = function (event) {
|
||||
is(event.target.getResponseHeader("Result-Content-Type"),
|
||||
file.type ? file.type : null,
|
||||
"request content-type in XMLHttpRequest send of " + testName);
|
||||
is(event.target.getResponseHeader("Result-Content-Length"),
|
||||
String(file.size),
|
||||
"request content-length in XMLHttpRequest send of " + testName);
|
||||
};
|
||||
|
||||
xhr.addEventListener("load", event => {
|
||||
XHRLoadHandler(event, resolve, contents, "XMLHttpRequest send of " + testName);
|
||||
});
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
xhr.send(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testSlice(file, size, type, contents, fileType, range) {
|
||||
is(file.type, type, fileType + " file is correct type");
|
||||
is(file.size, size, fileType + " file is correct size");
|
||||
ok(file instanceof File, fileType + " file is a File");
|
||||
ok(file instanceof Blob, fileType + " file is also a Blob");
|
||||
|
||||
let slice = file.slice(0, size);
|
||||
ok(slice instanceof Blob, fileType + " fullsize slice is a Blob");
|
||||
ok(!(slice instanceof File), fileType + " fullsize slice is not a File");
|
||||
|
||||
slice = file.slice(0, 1234);
|
||||
ok(slice instanceof Blob, fileType + " sized slice is a Blob");
|
||||
ok(!(slice instanceof File), fileType + " sized slice is not a File");
|
||||
|
||||
slice = file.slice(0, size, "foo/bar");
|
||||
is(slice.type, "foo/bar", fileType + " fullsize slice foo/bar type");
|
||||
|
||||
slice = file.slice(0, 5432, "foo/bar");
|
||||
is(slice.type, "foo/bar", fileType + " sized slice foo/bar type");
|
||||
|
||||
is(slice.slice(0, 10).type, "", fileType + " slice-slice type");
|
||||
is(slice.slice(0, 10).size, 10, fileType + " slice-slice size");
|
||||
is(slice.slice(0, 10, "hello/world").type, "hello/world", fileType + " slice-slice hello/world type");
|
||||
is(slice.slice(0, 10, "hello/world").size, 10, fileType + " slice-slice hello/world size");
|
||||
|
||||
// Start, end, expected size
|
||||
var indexes_range_1 = [[0, size, size],
|
||||
[0, 1234, 1234],
|
||||
[size-500, size, 500],
|
||||
[size-500, size+500, 500],
|
||||
[size+500, size+1500, 0],
|
||||
[0, 0, 0],
|
||||
[1000, 1000, 0],
|
||||
[size, size, 0],
|
||||
[undefined, undefined, size],
|
||||
[0, undefined, size],
|
||||
];
|
||||
|
||||
var indexes_range_2 = [[100, undefined, size-100],
|
||||
[-100, undefined, 100],
|
||||
[100, -100, size-200],
|
||||
[-size-100, undefined, size],
|
||||
[-2*size-100, 500, 500],
|
||||
[0, -size-100, 0],
|
||||
[100, -size-100, 0],
|
||||
[50, -size+100, 50],
|
||||
[0, 33000, 33000],
|
||||
[1000, 34000, 33000],
|
||||
];
|
||||
|
||||
let indexes;
|
||||
if (range == RANGE_1) {
|
||||
indexes = indexes_range_1;
|
||||
} else if (range == RANGE_2) {
|
||||
indexes = indexes_range_2;
|
||||
} else {
|
||||
throw "Invalid range!"
|
||||
}
|
||||
|
||||
function runNextTest() {
|
||||
if (indexes.length == 0) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
let index = indexes.shift();
|
||||
|
||||
let sliceContents;
|
||||
let testName;
|
||||
if (index[0] == undefined) {
|
||||
slice = file.slice();
|
||||
sliceContents = contents.slice();
|
||||
testName = fileType + " slice()";
|
||||
} else if (index[1] == undefined) {
|
||||
slice = file.slice(index[0]);
|
||||
sliceContents = contents.slice(index[0]);
|
||||
testName = fileType + " slice(" + index[0] + ")";
|
||||
} else {
|
||||
slice = file.slice(index[0], index[1]);
|
||||
sliceContents = contents.slice(index[0], index[1]);
|
||||
testName = fileType + " slice(" + index[0] + ", " + index[1] + ")";
|
||||
}
|
||||
|
||||
is(slice.type, "", testName + " type");
|
||||
is(slice.size, index[2], testName + " size");
|
||||
is(sliceContents.length, index[2], testName + " data size");
|
||||
|
||||
return testBlob(slice, sliceContents, testName).then(runNextTest);
|
||||
}
|
||||
|
||||
return runNextTest()
|
||||
.then(() => {
|
||||
// Slice of slice
|
||||
let slice = file.slice(0, 40000);
|
||||
return testBlob(slice.slice(5000, 42000), contents.slice(5000, 40000), "file slice slice");
|
||||
})
|
||||
.then(() => {
|
||||
// ...of slice of slice
|
||||
let slice = file.slice(0, 40000).slice(5000, 42000).slice(400, 700);
|
||||
SpecialPowers.gc();
|
||||
return testBlob(slice, contents.slice(5400, 5700), "file slice slice slice");
|
||||
});
|
||||
}
|
||||
|
||||
function convertXHRBinary(s) {
|
||||
let res = "";
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
res += String.fromCharCode(s.charCodeAt(i) & 255);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function XHRLoadHandler(event, resolve, contents, testName) {
|
||||
is(event.target.readyState, 4, "[XHR] readyState in test " + testName);
|
||||
is(event.target.status, 200, "[XHR] no error in test " + testName);
|
||||
// Do not use |is(convertXHRBinary(event.target.responseText), contents, "...");| that may output raw binary data.
|
||||
let convertedData = convertXHRBinary(event.target.responseText);
|
||||
is(convertedData.length, contents.length, "[XHR] Length of result in test " + testName);
|
||||
ok(convertedData == contents, "[XHR] Content of result in test " + testName);
|
||||
is(event.lengthComputable, event.total != 0, "[XHR] lengthComputable in test " + testName);
|
||||
is(event.loaded, contents.length, "[XHR] Loaded length in test " + testName);
|
||||
is(event.total, contents.length, "[XHR] Total length in test " + testName);
|
||||
resolve();
|
||||
}
|
||||
|
||||
function checkMPSubmission(sub, expected) {
|
||||
function getPropCount(o) {
|
||||
let x, l = 0;
|
||||
for (x in o) ++l;
|
||||
return l;
|
||||
}
|
||||
|
||||
is(sub.length, expected.length,
|
||||
"Correct number of items");
|
||||
let i;
|
||||
for (i = 0; i < expected.length; ++i) {
|
||||
if (!("fileName" in expected[i])) {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + expected[i].name + "\"",
|
||||
"Correct name (A)");
|
||||
is (getPropCount(sub[i].headers), 1,
|
||||
"Wrong number of headers (A)");
|
||||
}
|
||||
else {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + expected[i].name + "\"; filename=\"" +
|
||||
expected[i].fileName + "\"",
|
||||
"Correct name (B)");
|
||||
is(sub[i].headers["Content-Type"],
|
||||
expected[i].contentType,
|
||||
"Correct content type (B)");
|
||||
is (getPropCount(sub[i].headers), 2,
|
||||
"Wrong number of headers (B)");
|
||||
}
|
||||
// Do not use |is(sub[i].body, expected[i].value, "...");| that may output raw binary data.
|
||||
is(sub[i].body.length, expected[i].value.length,
|
||||
"Length of correct value");
|
||||
ok(sub[i].body == expected[i].value,
|
||||
"Content of correct value");
|
||||
}
|
||||
}
|
||||
|
||||
function createCanvasURL() {
|
||||
return new Promise(resolve => {
|
||||
// Create a decent-sized image
|
||||
let cx = $("canvas").getContext('2d');
|
||||
let s = cx.canvas.width;
|
||||
let grad = cx.createLinearGradient(0, 0, s-1, s-1);
|
||||
for (i = 0; i < 0.95; i += .1) {
|
||||
grad.addColorStop(i, "white");
|
||||
grad.addColorStop(i + .05, "black");
|
||||
}
|
||||
grad.addColorStop(1, "white");
|
||||
cx.fillStyle = grad;
|
||||
cx.fillRect(0, 0, s-1, s-1);
|
||||
cx.fillStyle = "rgba(200, 0, 0, 0.9)";
|
||||
cx.fillRect(.1 * s, .1 * s, .7 * s, .7 * s);
|
||||
cx.strokeStyle = "rgba(0, 0, 130, 0.5)";
|
||||
cx.lineWidth = .14 * s;
|
||||
cx.beginPath();
|
||||
cx.arc(.6 * s, .6 * s, .3 * s, 0, Math.PI*2, true);
|
||||
cx.stroke();
|
||||
cx.closePath();
|
||||
cx.fillStyle = "rgb(0, 255, 0)";
|
||||
cx.beginPath();
|
||||
cx.arc(.1 * s, .8 * s, .1 * s, 0, Math.PI*2, true);
|
||||
cx.fill();
|
||||
cx.closePath();
|
||||
|
||||
let data = atob(cx.canvas.toDataURL("image/png").substring("data:text/png;base64,".length + 1));
|
||||
|
||||
// This might fail if we dramatically improve the png encoder. If that happens
|
||||
// please increase the complexity or size of the image generated above to ensure
|
||||
// that we're testing with files that are large enough.
|
||||
ok(data.length > 65536, "test data sufficiently large");
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
|
||||
function createFile(data, name) {
|
||||
return new Promise(resolve => {
|
||||
SpecialPowers.createFiles([{name, data}],
|
||||
files => { resolve(files[0]); });
|
||||
});
|
||||
}
|
580
dom/file/tests/common_fileReader.js
Normal file
580
dom/file/tests/common_fileReader.js
Normal file
@ -0,0 +1,580 @@
|
||||
function test_setup() {
|
||||
return new Promise(resolve => {
|
||||
const minFileSize = 20000;
|
||||
|
||||
// Create strings containing data we'll test with. We'll want long
|
||||
// strings to ensure they span multiple buffers while loading
|
||||
let testTextData = "asd b\tlah\u1234w\u00a0r";
|
||||
while (testTextData.length < minFileSize) {
|
||||
testTextData = testTextData + testTextData;
|
||||
}
|
||||
|
||||
let testASCIIData = "abcdef 123456\n";
|
||||
while (testASCIIData.length < minFileSize) {
|
||||
testASCIIData = testASCIIData + testASCIIData;
|
||||
}
|
||||
|
||||
let testBinaryData = "";
|
||||
for (let i = 0; i < 256; i++) {
|
||||
testBinaryData += String.fromCharCode(i);
|
||||
}
|
||||
while (testBinaryData.length < minFileSize) {
|
||||
testBinaryData = testBinaryData + testBinaryData;
|
||||
}
|
||||
|
||||
let dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
|
||||
testBinaryData.length % 3);
|
||||
let dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
|
||||
testBinaryData.length % 3);
|
||||
let dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
|
||||
testBinaryData.length % 3);
|
||||
|
||||
|
||||
//Set up files for testing
|
||||
let openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
|
||||
let opener = SpecialPowers.loadChromeScript(openerURL);
|
||||
|
||||
opener.addMessageListener("files.opened", message => {
|
||||
let [
|
||||
asciiFile,
|
||||
binaryFile,
|
||||
nonExistingFile,
|
||||
utf8TextFile,
|
||||
utf16TextFile,
|
||||
emptyFile,
|
||||
dataUrlFile0,
|
||||
dataUrlFile1,
|
||||
dataUrlFile2,
|
||||
] = message;
|
||||
|
||||
resolve({ blobs:{ asciiFile, binaryFile, nonExistingFile, utf8TextFile,
|
||||
utf16TextFile, emptyFile, dataUrlFile0, dataUrlFile1,
|
||||
dataUrlFile2 },
|
||||
data: { text: testTextData,
|
||||
ascii: testASCIIData,
|
||||
binary: testBinaryData,
|
||||
url0: dataurldata0,
|
||||
url1: dataurldata1,
|
||||
url2: dataurldata2, }});
|
||||
});
|
||||
|
||||
opener.sendAsyncMessage("files.open", [
|
||||
testASCIIData,
|
||||
testBinaryData,
|
||||
null,
|
||||
convertToUTF8(testTextData),
|
||||
convertToUTF16(testTextData),
|
||||
"",
|
||||
dataurldata0,
|
||||
dataurldata1,
|
||||
dataurldata2,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function runBasicTests(data) {
|
||||
return test_basic()
|
||||
.then(() => {
|
||||
return test_readAsText(data.blobs.asciiFile, data.data.ascii);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsBinaryString(data.blobs.binaryFile, data.data.binary);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsArrayBuffer(data.blobs.binaryFile, data.data.binary);
|
||||
});
|
||||
}
|
||||
|
||||
function runEncodingTests(data) {
|
||||
return test_readAsTextWithEncoding(data.blobs.asciiFile, data.data.ascii,
|
||||
data.data.ascii.length, "")
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.asciiFile, data.data.ascii,
|
||||
data.data.ascii.length, "iso8859-1");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.utf8TextFile, data.data.text,
|
||||
convertToUTF8(data.data.text).length,
|
||||
"utf8");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.utf16TextFile, data.data.text,
|
||||
convertToUTF16(data.data.text).length,
|
||||
"utf-16");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "utf8");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsTextWithEncoding(data.blobs.emptyFile, "", 0, "utf-16");
|
||||
});
|
||||
}
|
||||
|
||||
function runEmptyTests(data) {
|
||||
return test_onlyResult()
|
||||
.then(() => {
|
||||
return test_readAsText(data.blobs.emptyFile, "");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsBinaryString(data.blobs.emptyFile, "");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsArrayBuffer(data.blobs.emptyFile, "");
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsDataURL(data.blobs.emptyFile, convertToDataURL(""), 0);
|
||||
});
|
||||
}
|
||||
|
||||
function runTwiceTests(data) {
|
||||
return test_readAsTextTwice(data.blobs.asciiFile, data.data.ascii)
|
||||
.then(() => {
|
||||
return test_readAsBinaryStringTwice(data.blobs.binaryFile,
|
||||
data.data.binary);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsDataURLTwice(data.blobs.binaryFile,
|
||||
convertToDataURL(data.data.binary),
|
||||
data.data.binary.length);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsArrayBufferTwice(data.blobs.binaryFile,
|
||||
data.data.binary);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsArrayBufferTwice2(data.blobs.binaryFile,
|
||||
data.data.binary);
|
||||
});
|
||||
}
|
||||
|
||||
function runOtherTests(data) {
|
||||
return test_readAsDataURL_customLength(data.blobs.dataUrlFile0,
|
||||
convertToDataURL(data.data.url0),
|
||||
data.data.url0.length, 0)
|
||||
.then(() => {
|
||||
return test_readAsDataURL_customLength(data.blobs.dataUrlFile1,
|
||||
convertToDataURL(data.data.url1),
|
||||
data.data.url1.length, 1);
|
||||
})
|
||||
.then(() => {
|
||||
return test_readAsDataURL_customLength(data.blobs.dataUrlFile2,
|
||||
convertToDataURL(data.data.url2),
|
||||
data.data.url2.length, 2);
|
||||
})
|
||||
.then(() => {
|
||||
return test_abort(data.blobs.asciiFile);
|
||||
})
|
||||
.then(() => {
|
||||
return test_abort_readAsX(data.blobs.asciiFile, data.data.ascii);
|
||||
})
|
||||
.then(() => {
|
||||
return test_nonExisting(data.blobs.nonExistingFile);
|
||||
});
|
||||
}
|
||||
|
||||
function convertToUTF16(s) {
|
||||
let res = "";
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
c = s.charCodeAt(i);
|
||||
res += String.fromCharCode(c & 255, c >>> 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function convertToUTF8(s) {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
function convertToDataURL(s) {
|
||||
return "data:application/octet-stream;base64," + btoa(s);
|
||||
}
|
||||
|
||||
function loadEventHandler_string(event, resolve, reader, data, dataLength, testName) {
|
||||
is(event.target, reader, "Correct target.");
|
||||
is(event.target.readyState, FileReader.DONE, "readyState in test " + testName);
|
||||
is(event.target.error, null, "no error in test " + testName);
|
||||
is(event.target.result, data, "result in test " + testName);
|
||||
is(event.lengthComputable, true, "lengthComputable in test " + testName);
|
||||
is(event.loaded, dataLength, "loaded in test " + testName);
|
||||
is(event.total, dataLength, "total in test " + testName);
|
||||
resolve();
|
||||
}
|
||||
|
||||
function loadEventHandler_arrayBuffer(event, resolve, reader, data, testName) {
|
||||
is(event.target.readyState, FileReader.DONE, "readyState in test " + testName);
|
||||
is(event.target.error, null, "no error in test " + testName);
|
||||
is(event.lengthComputable, true, "lengthComputable in test " + testName);
|
||||
is(event.loaded, data.length, "loaded in test " + testName);
|
||||
is(event.total, data.length, "total in test " + testName);
|
||||
is(event.target.result.byteLength, data.length, "array buffer size in test " + testName);
|
||||
|
||||
let u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), data,
|
||||
"array buffer contents in test " + testName);
|
||||
u8v = null;
|
||||
|
||||
if ("SpecialPowers" in self) {
|
||||
SpecialPowers.gc();
|
||||
|
||||
is(event.target.result.byteLength, data.length,
|
||||
"array buffer size after gc in test " + testName);
|
||||
u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), data,
|
||||
"array buffer contents after gc in test " + testName);
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
function test_basic() {
|
||||
return new Promise(resolve => {
|
||||
is(FileReader.EMPTY, 0, "correct EMPTY value");
|
||||
is(FileReader.LOADING, 1, "correct LOADING value");
|
||||
is(FileReader.DONE, 2, "correct DONE value");
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsText(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let onloadHasRun = false;
|
||||
let onloadStartHasRun = false;
|
||||
|
||||
let r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
|
||||
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "readAsText");
|
||||
}
|
||||
|
||||
r.addEventListener("load", () => { onloadHasRun = true });
|
||||
r.addEventListener("loadstart", () => { onloadStartHasRun = true });
|
||||
|
||||
r.readAsText(blob);
|
||||
|
||||
is(r.readyState, FileReader.LOADING, "correct loading text readyState");
|
||||
is(onloadHasRun, false, "text loading must be async");
|
||||
is(onloadStartHasRun, true, "text loadstart should fire sync");
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsBinaryString(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let onloadHasRun = false;
|
||||
let onloadStartHasRun = false;
|
||||
|
||||
let r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
|
||||
|
||||
r.addEventListener("load", function() { onloadHasRun = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRun = true });
|
||||
|
||||
r.readAsBinaryString(blob);
|
||||
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "readAsBinaryString");
|
||||
}
|
||||
|
||||
is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
|
||||
is(onloadHasRun, false, "binary loading must be async");
|
||||
is(onloadStartHasRun, true, "binary loadstart should fire sync");
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsArrayBuffer(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let onloadHasRun = false;
|
||||
let onloadStartHasRun = false;
|
||||
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
|
||||
|
||||
r.addEventListener("load", function() { onloadHasRun = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRun = true });
|
||||
|
||||
r.readAsArrayBuffer(blob);
|
||||
|
||||
r.onload = event => {
|
||||
loadEventHandler_arrayBuffer(event, resolve, r, text, "readAsArrayBuffer");
|
||||
}
|
||||
|
||||
is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
|
||||
is(onloadHasRun, false, "arrayBuffer loading must be async");
|
||||
is(onloadStartHasRun, true, "arrayBuffer loadstart should fire sync");
|
||||
});
|
||||
}
|
||||
|
||||
// Test a variety of encodings, and make sure they work properly
|
||||
function test_readAsTextWithEncoding(blob, text, length, charset) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, length, "readAsText-" + charset);
|
||||
}
|
||||
r.readAsText(blob, charset);
|
||||
});
|
||||
}
|
||||
|
||||
// Test get result without reading
|
||||
function test_onlyResult() {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "readyState in test reader get result without reading");
|
||||
is(r.error, null, "no error in test reader get result without reading");
|
||||
is(r.result, null, "result in test reader get result without reading");
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsDataURL(blob, text, length) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, length, "readAsDataURL");
|
||||
}
|
||||
r.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Test reusing a FileReader to read multiple times
|
||||
function test_readAsTextTwice(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, () => {}, r, text, text.length, "readAsText-reused-once");
|
||||
}
|
||||
|
||||
let anotherListener = event => {
|
||||
let r = event.target;
|
||||
r.removeEventListener("load", anotherListener);
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "readAsText-reused-twice");
|
||||
}
|
||||
r.readAsText(blob);
|
||||
};
|
||||
|
||||
r.addEventListener("load", anotherListener);
|
||||
r.readAsText(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Test reusing a FileReader to read multiple times
|
||||
function test_readAsBinaryStringTwice(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, () => {}, r, text, text.length, "readAsBinaryString-reused-once");
|
||||
}
|
||||
|
||||
let anotherListener = event => {
|
||||
let r = event.target;
|
||||
r.removeEventListener("load", anotherListener);
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "readAsBinaryString-reused-twice");
|
||||
}
|
||||
r.readAsBinaryString(blob);
|
||||
};
|
||||
|
||||
r.addEventListener("load", anotherListener);
|
||||
r.readAsBinaryString(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsDataURLTwice(blob, text, length) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, () => {}, r, text, length, "readAsDataURL-reused-once");
|
||||
}
|
||||
|
||||
let anotherListener = event => {
|
||||
let r = event.target;
|
||||
r.removeEventListener("load", anotherListener);
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, length, "readAsDataURL-reused-twice");
|
||||
}
|
||||
r.readAsDataURL(blob);
|
||||
};
|
||||
|
||||
r.addEventListener("load", anotherListener);
|
||||
r.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsArrayBufferTwice(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_arrayBuffer(event, () => {}, r, text, "readAsArrayBuffer-reused-once");
|
||||
}
|
||||
|
||||
let anotherListener = event => {
|
||||
let r = event.target;
|
||||
r.removeEventListener("load", anotherListener);
|
||||
r.onload = event => {
|
||||
loadEventHandler_arrayBuffer(event, resolve, r, text, "readAsArrayBuffer-reused-twice");
|
||||
}
|
||||
r.readAsArrayBuffer(blob);
|
||||
};
|
||||
|
||||
r.addEventListener("load", anotherListener);
|
||||
r.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Test first reading as ArrayBuffer then read as something else (BinaryString)
|
||||
// and doesn't crash
|
||||
function test_readAsArrayBufferTwice2(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_arrayBuffer(event, () => {}, r, text, "readAsArrayBuffer-reused-once2");
|
||||
}
|
||||
|
||||
let anotherListener = event => {
|
||||
let r = event.target;
|
||||
r.removeEventListener("load", anotherListener);
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "readAsArrayBuffer-reused-twice2");
|
||||
}
|
||||
r.readAsBinaryString(blob);
|
||||
};
|
||||
|
||||
r.addEventListener("load", anotherListener);
|
||||
r.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function test_readAsDataURL_customLength(blob, text, length, numb) {
|
||||
return new Promise(resolve => {
|
||||
is(length % 3, numb, "Want to test data with length %3 == " + numb);
|
||||
let r = new FileReader();
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, length, "dataurl reading, %3 = " + numb);
|
||||
}
|
||||
r.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Test abort()
|
||||
function test_abort(blob) {
|
||||
return new Promise(resolve => {
|
||||
let abortHasRun = false;
|
||||
let loadEndHasRun = false;
|
||||
|
||||
let r = new FileReader();
|
||||
|
||||
r.onabort = function (event) {
|
||||
is(abortHasRun, false, "abort should only fire once");
|
||||
is(loadEndHasRun, false, "loadend shouldn't have fired yet");
|
||||
abortHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
|
||||
r.onloadend = function (event) {
|
||||
is(abortHasRun, true, "abort should fire before loadend");
|
||||
is(loadEndHasRun, false, "loadend should only fire once");
|
||||
loadEndHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
|
||||
r.onload = function() { ok(false, "load should not fire for aborted reads") };
|
||||
r.onerror = function() { ok(false, "error should not fire for aborted reads") };
|
||||
r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
|
||||
|
||||
let abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
|
||||
is(abortThrew, false, "abort() doesn't throw");
|
||||
is(abortHasRun, false, "abort() is a no-op unless loading");
|
||||
|
||||
r.readAsText(blob);
|
||||
r.abort();
|
||||
|
||||
is(abortHasRun, true, "abort should fire sync");
|
||||
is(loadEndHasRun, true, "loadend should fire sync");
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// Test calling readAsX to cause abort()
|
||||
function test_abort_readAsX(blob, text) {
|
||||
return new Promise(resolve => {
|
||||
let reuseAbortHasRun = false;
|
||||
|
||||
let r = new FileReader();
|
||||
r.onabort = function (event) {
|
||||
is(reuseAbortHasRun, false, "abort should only fire once");
|
||||
reuseAbortHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onload = function() { ok(false, "load should fire for nested reads"); };
|
||||
|
||||
let abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
|
||||
is(abortThrew, false, "abort() should not throw");
|
||||
is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
|
||||
r.readAsText(blob);
|
||||
|
||||
let readThrew = false;
|
||||
try {
|
||||
r.readAsText(blob);
|
||||
} catch(e) {
|
||||
readThrew = true;
|
||||
}
|
||||
|
||||
is(readThrew, true, "readAsText() must throw if loading");
|
||||
is(reuseAbortHasRun, false, "abort should not fire");
|
||||
|
||||
r.onload = event => {
|
||||
loadEventHandler_string(event, resolve, r, text, text.length, "reuse-as-abort reading");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test reading from nonexistent files
|
||||
function test_nonExisting(blob) {
|
||||
return new Promise(resolve => {
|
||||
let r = new FileReader();
|
||||
|
||||
r.onerror = function (event) {
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
|
||||
is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
resolve();
|
||||
};
|
||||
r.onload = function (event) {
|
||||
is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
|
||||
};
|
||||
|
||||
let didThrow = false;
|
||||
try {
|
||||
r.readAsDataURL(blob);
|
||||
} catch(ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
|
||||
// Once this test passes, we should test that onerror gets called and
|
||||
// that the FileReader object is in the right state during that call.
|
||||
is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
|
||||
});
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.importGlobalProperties(["File"]);
|
||||
|
||||
var fileNum = 1;
|
||||
|
||||
function createFileWithData(fileData) {
|
||||
var willDelete = fileData === null;
|
||||
|
||||
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
|
||||
|
||||
var testFile = dirSvc.get("ProfD", Ci.nsIFile);
|
||||
testFile.append("fileAPItestfile" + fileNum);
|
||||
fileNum++;
|
||||
testFile.append("fileAPItestfile");
|
||||
testFile.createUnique(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
|
||||
|
||||
var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
|
||||
outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
|
||||
0o666, 0);
|
||||
|
@ -1,244 +0,0 @@
|
||||
// Utility functions
|
||||
var testRanCounter = 0;
|
||||
var expectedTestCount = 0;
|
||||
|
||||
function testHasRun() {
|
||||
++testRanCounter;
|
||||
if (testRanCounter == expectedTestCount) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testFile(file, contents, test) {
|
||||
SimpleTest.requestLongerTimeout(2);
|
||||
|
||||
// Load file using FileReader
|
||||
var r = new FileReader();
|
||||
r.onload = getFileReaderLoadHandler(contents, contents.length, "FileReader.readAsBinaryString of " + test);
|
||||
r.readAsBinaryString(file);
|
||||
expectedTestCount++;
|
||||
|
||||
// Load file using URL.createObjectURL and XMLHttpRequest
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open("GET", URL.createObjectURL(file));
|
||||
xhr.onload = getXHRLoadHandler(contents, contents.length,
|
||||
"XMLHttpRequest load of " + test);
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
xhr.send();
|
||||
expectedTestCount++;
|
||||
|
||||
// Send file to server using FormData and XMLHttpRequest
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.onload = function(event) {
|
||||
checkMPSubmission(JSON.parse(event.target.responseText),
|
||||
[{ name: "hello", value: "world"},
|
||||
{ name: "myfile",
|
||||
value: contents,
|
||||
fileName: file.name || "blob",
|
||||
contentType: file.type || "application/octet-stream" }]);
|
||||
testHasRun();
|
||||
}
|
||||
xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
|
||||
var fd = new FormData;
|
||||
fd.append("hello", "world");
|
||||
fd.append("myfile", file);
|
||||
xhr.send(fd);
|
||||
expectedTestCount++;
|
||||
|
||||
// Send file to server using plain XMLHttpRequest
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs");
|
||||
xhr.onload = function (event) {
|
||||
is(event.target.getResponseHeader("Result-Content-Type"),
|
||||
file.type ? file.type : null,
|
||||
"request content-type in XMLHttpRequest send of " + test);
|
||||
is(event.target.getResponseHeader("Result-Content-Length"),
|
||||
String(file.size),
|
||||
"request content-length in XMLHttpRequest send of " + test);
|
||||
};
|
||||
xhr.addEventListener("load",
|
||||
getXHRLoadHandler(contents, contents.length,
|
||||
"XMLHttpRequest send of " + test));
|
||||
xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
xhr.send(file);
|
||||
expectedTestCount++;
|
||||
}
|
||||
|
||||
function getFileReaderLoadHandler(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"[FileReader] readyState in test " + testName);
|
||||
is(event.target.error, null,
|
||||
"[FileReader] no error in test " + testName);
|
||||
// Do not use |is(event.target.result, expectedResult, "...");| that may output raw binary data.
|
||||
is(event.target.result.length, expectedResult.length,
|
||||
"[FileReader] Length of result in test " + testName);
|
||||
ok(event.target.result == expectedResult,
|
||||
"[FileReader] Content of result in test " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"[FileReader] lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"[FileReader] Loaded length in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"[FileReader] Total length in test " + testName);
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function getXHRLoadHandler(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, 4,
|
||||
"[XHR] readyState in test " + testName);
|
||||
is(event.target.status, 200,
|
||||
"[XHR] no error in test " + testName);
|
||||
// Do not use |is(convertXHRBinary(event.target.responseText), expectedResult, "...");| that may output raw binary data.
|
||||
var convertedData = convertXHRBinary(event.target.responseText);
|
||||
is(convertedData.length, expectedResult.length,
|
||||
"[XHR] Length of result in test " + testName);
|
||||
ok(convertedData == expectedResult,
|
||||
"[XHR] Content of result in test " + testName);
|
||||
is(event.lengthComputable, event.total != 0,
|
||||
"[XHR] lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"[XHR] Loaded length in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"[XHR] Total length in test " + testName);
|
||||
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function convertXHRBinary(s) {
|
||||
var res = "";
|
||||
for (var i = 0; i < s.length; ++i) {
|
||||
res += String.fromCharCode(s.charCodeAt(i) & 255);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function gc() {
|
||||
window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
.getInterface(SpecialPowers.Ci.nsIDOMWindowUtils)
|
||||
.garbageCollect();
|
||||
}
|
||||
|
||||
function checkMPSubmission(sub, expected) {
|
||||
function getPropCount(o) {
|
||||
var x, l = 0;
|
||||
for (x in o) ++l;
|
||||
return l;
|
||||
}
|
||||
|
||||
is(sub.length, expected.length,
|
||||
"Correct number of items");
|
||||
var i;
|
||||
for (i = 0; i < expected.length; ++i) {
|
||||
if (!("fileName" in expected[i])) {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + expected[i].name + "\"",
|
||||
"Correct name (A)");
|
||||
is (getPropCount(sub[i].headers), 1,
|
||||
"Wrong number of headers (A)");
|
||||
}
|
||||
else {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + expected[i].name + "\"; filename=\"" +
|
||||
expected[i].fileName + "\"",
|
||||
"Correct name (B)");
|
||||
is(sub[i].headers["Content-Type"],
|
||||
expected[i].contentType,
|
||||
"Correct content type (B)");
|
||||
is (getPropCount(sub[i].headers), 2,
|
||||
"Wrong number of headers (B)");
|
||||
}
|
||||
// Do not use |is(sub[i].body, expected[i].value, "...");| that may output raw binary data.
|
||||
is(sub[i].body.length, expected[i].value.length,
|
||||
"Length of correct value");
|
||||
ok(sub[i].body == expected[i].value,
|
||||
"Content of correct value");
|
||||
}
|
||||
}
|
||||
|
||||
function testSlice(file, size, type, contents, fileType) {
|
||||
is(file.type, type, fileType + " file is correct type");
|
||||
is(file.size, size, fileType + " file is correct size");
|
||||
ok(file instanceof File, fileType + " file is a File");
|
||||
ok(file instanceof Blob, fileType + " file is also a Blob");
|
||||
|
||||
var slice = file.slice(0, size);
|
||||
ok(slice instanceof Blob, fileType + " fullsize slice is a Blob");
|
||||
ok(!(slice instanceof File), fileType + " fullsize slice is not a File");
|
||||
|
||||
slice = file.slice(0, 1234);
|
||||
ok(slice instanceof Blob, fileType + " sized slice is a Blob");
|
||||
ok(!(slice instanceof File), fileType + " sized slice is not a File");
|
||||
|
||||
slice = file.slice(0, size, "foo/bar");
|
||||
is(slice.type, "foo/bar", fileType + " fullsize slice foo/bar type");
|
||||
|
||||
slice = file.slice(0, 5432, "foo/bar");
|
||||
is(slice.type, "foo/bar", fileType + " sized slice foo/bar type");
|
||||
|
||||
is(slice.slice(0, 10).type, "", fileType + " slice-slice type");
|
||||
is(slice.slice(0, 10).size, 10, fileType + " slice-slice size");
|
||||
is(slice.slice(0, 10, "hello/world").type, "hello/world", fileType + " slice-slice hello/world type");
|
||||
is(slice.slice(0, 10, "hello/world").size, 10, fileType + " slice-slice hello/world size");
|
||||
|
||||
// Start, end, expected size
|
||||
var indexes = [[0, size, size],
|
||||
[0, 1234, 1234],
|
||||
[size-500, size, 500],
|
||||
[size-500, size+500, 500],
|
||||
[size+500, size+1500, 0],
|
||||
[0, 0, 0],
|
||||
[1000, 1000, 0],
|
||||
[size, size, 0],
|
||||
[undefined, undefined, size],
|
||||
[0, undefined, size],
|
||||
[100, undefined, size-100],
|
||||
[-100, undefined, 100],
|
||||
[100, -100, size-200],
|
||||
[-size-100, undefined, size],
|
||||
[-2*size-100, 500, 500],
|
||||
[0, -size-100, 0],
|
||||
[100, -size-100, 0],
|
||||
[50, -size+100, 50],
|
||||
[0, 33000, 33000],
|
||||
[1000, 34000, 33000],
|
||||
];
|
||||
|
||||
for (var i = 0; i < indexes.length; ++i) {
|
||||
var sliceContents;
|
||||
var testName;
|
||||
if (indexes[i][0] == undefined) {
|
||||
slice = file.slice();
|
||||
sliceContents = contents.slice();
|
||||
testName = fileType + " slice()";
|
||||
}
|
||||
else if (indexes[i][1] == undefined) {
|
||||
slice = file.slice(indexes[i][0]);
|
||||
sliceContents = contents.slice(indexes[i][0]);
|
||||
testName = fileType + " slice(" + indexes[i][0] + ")";
|
||||
}
|
||||
else {
|
||||
slice = file.slice(indexes[i][0], indexes[i][1]);
|
||||
sliceContents = contents.slice(indexes[i][0], indexes[i][1]);
|
||||
testName = fileType + " slice(" + indexes[i][0] + ", " + indexes[i][1] + ")";
|
||||
}
|
||||
is(slice.type, "", testName + " type");
|
||||
is(slice.size, indexes[i][2], testName + " size");
|
||||
is(sliceContents.length, indexes[i][2], testName + " data size");
|
||||
testFile(slice, sliceContents, testName);
|
||||
}
|
||||
|
||||
// Slice of slice
|
||||
var slice = file.slice(0, 40000);
|
||||
testFile(slice.slice(5000, 42000), contents.slice(5000, 40000), "file slice slice");
|
||||
|
||||
// ...of slice of slice
|
||||
slice = slice.slice(5000, 42000).slice(400, 700);
|
||||
SpecialPowers.gc();
|
||||
testFile(slice, contents.slice(5400, 5700), "file slice slice slice");
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
common_blob.js
|
||||
create_file_objects.js
|
||||
common_fileReader.js
|
||||
file_blobURL_expiring.html
|
||||
file_mozfiledataurl_img.jpg
|
||||
file_mozfiledataurl_audio.ogg
|
||||
@ -8,8 +10,8 @@ support-files =
|
||||
file_mozfiledataurl_text.txt
|
||||
file_mozfiledataurl_inner.html
|
||||
file_nonascii_blob_url.html
|
||||
fileutils.js
|
||||
fileapi_chromeScript.js
|
||||
worker_fileReader.js
|
||||
!/dom/html/test/form_submit_server.sjs
|
||||
!/dom/xhr/tests/file_XHRSendData.sjs
|
||||
|
||||
@ -21,8 +23,23 @@ support-files =
|
||||
support-files = file_ipc_messagemanager_blob.html
|
||||
[test_nonascii_blob_url.html]
|
||||
[test_file_negative_date.html]
|
||||
[test_fileapi.html]
|
||||
[test_fileapi_slice.html]
|
||||
[test_fileapi_basic.html]
|
||||
[test_fileapi_encoding.html]
|
||||
[test_fileapi_twice.html]
|
||||
[test_fileapi_other.html]
|
||||
[test_fileapi_basic_worker.html]
|
||||
[test_fileapi_encoding_worker.html]
|
||||
[test_fileapi_twice_worker.html]
|
||||
[test_fileapi_other_worker.html]
|
||||
[test_fileapi_slice_realFile_1.html]
|
||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_fileapi_slice_realFile_2.html]
|
||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_fileapi_slice_memFile_1.html]
|
||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_fileapi_slice_memFile_2.html]
|
||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_fileapi_slice_image.html]
|
||||
skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_mozfiledataurl.html]
|
||||
skip-if = toolkit == 'android' #TIMED_OUT
|
||||
|
@ -7,7 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=721569
|
||||
<title>Test for Blob constructor (Bug 721569)</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="fileutils.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
@ -196,49 +196,49 @@ let testData =
|
||||
{start: 4, length: 8, contents: "DEFGHIJK"}]]
|
||||
];
|
||||
|
||||
let currentTest = null;
|
||||
let testCounter = 0;
|
||||
|
||||
function doTest(data) {
|
||||
testCounter++;
|
||||
|
||||
var [blobs, options, tests] = data;
|
||||
|
||||
function runTest(test) {
|
||||
|
||||
let blob;
|
||||
if (options !== undefined) {
|
||||
blob = new Blob(blobs, options);
|
||||
} else {
|
||||
blob = new Blob(blobs);
|
||||
function runTests() {
|
||||
if (!currentTest || currentTest[2].length == 0) {
|
||||
if (testData.length == 0) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
ok(blob, "Test " + testCounter + " got blob");
|
||||
ok(blob instanceof Blob, "Test " + testCounter + " blob is a Blob");
|
||||
ok(!(blob instanceof File), "Test " + testCounter + " blob is not a File");
|
||||
|
||||
let slice = blob.slice(test.start, test.start + test.length);
|
||||
ok(slice, "Test " + testCounter + " got slice");
|
||||
ok(slice instanceof Blob, "Test " + testCounter + " slice is a Blob");
|
||||
ok(!(slice instanceof File), "Test " + testCounter + " slice is not a File");
|
||||
is(slice.size, test.contents.length,
|
||||
"Test " + testCounter + " slice is correct size");
|
||||
|
||||
testFile(slice, test.contents, "Test " + testCounter);
|
||||
currentTest = testData.shift();
|
||||
++testCounter;
|
||||
}
|
||||
if (Array.isArray(tests)) {
|
||||
tests.forEach(runTest);
|
||||
|
||||
let [blobs, options] = currentTest;
|
||||
let test = currentTest[2].shift();
|
||||
|
||||
let blob;
|
||||
if (options !== undefined) {
|
||||
blob = new Blob(blobs, options);
|
||||
} else {
|
||||
try {
|
||||
let blob = new Blob(blobs, options);
|
||||
ok(false, "NOT REACHED");
|
||||
} catch (e) {
|
||||
is(e.name, tests, "Blob constructor should throw " + tests);
|
||||
}
|
||||
blob = new Blob(blobs);
|
||||
}
|
||||
SpecialPowers.gc();
|
||||
|
||||
ok(blob, "Test " + testCounter + " got blob");
|
||||
ok(blob instanceof Blob, "Test " + testCounter + " blob is a Blob");
|
||||
ok(!(blob instanceof File), "Test " + testCounter + " blob is not a File");
|
||||
|
||||
let slice = blob.slice(test.start, test.start + test.length);
|
||||
ok(slice, "Test " + testCounter + " got slice");
|
||||
ok(slice instanceof Blob, "Test " + testCounter + " slice is a Blob");
|
||||
ok(!(slice instanceof File), "Test " + testCounter + " slice is not a File");
|
||||
is(slice.size, test.contents.length, "Test " + testCounter + " slice is correct size");
|
||||
|
||||
testBlob(slice, test.contents, "Test " + testCounter).then(() => {
|
||||
SpecialPowers.gc();
|
||||
runTests();
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.requestLongerTimeout(2);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
testData.forEach(doTest);
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -7,16 +7,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1158437
|
||||
<title>Test for negative date in File (Bug 1158437)</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="fileutils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1158437">Mozilla Bug 1158437</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
@ -33,7 +28,6 @@ is(f3.lastModified, -1000, "lastModified == -1000 is supported");
|
||||
ok(f3.lastModifiedDate.toString(), (new Date(-1000)).toString(), "Correct f3.lastModifiedDate value");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -1,487 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=414796
|
||||
-->
|
||||
<title>Test for Bug 414796</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=414796">Mozilla Bug 414796</a>
|
||||
<p id="display">
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
// File constructors should not work from non-chrome code
|
||||
try {
|
||||
var file = File("/etc/passwd");
|
||||
ok(false, "Did not throw on unprivileged attempt to construct a File");
|
||||
} catch (e) {
|
||||
ok(true, "Threw on an unprivileged attempt to construct a File");
|
||||
}
|
||||
|
||||
const minFileSize = 20000;
|
||||
var testRanCounter = 0;
|
||||
var expectedTestCount = 0;
|
||||
var testSetupFinished = false;
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
is(FileReader.EMPTY, 0, "correct EMPTY value");
|
||||
is(FileReader.LOADING, 1, "correct LOADING value");
|
||||
is(FileReader.DONE, 2, "correct DONE value");
|
||||
|
||||
// Create strings containing data we'll test with. We'll want long
|
||||
// strings to ensure they span multiple buffers while loading
|
||||
var testTextData = "asd b\tlah\u1234w\u00a0r";
|
||||
while (testTextData.length < minFileSize) {
|
||||
testTextData = testTextData + testTextData;
|
||||
}
|
||||
|
||||
var testASCIIData = "abcdef 123456\n";
|
||||
while (testASCIIData.length < minFileSize) {
|
||||
testASCIIData = testASCIIData + testASCIIData;
|
||||
}
|
||||
|
||||
var testBinaryData = "";
|
||||
for (var i = 0; i < 256; i++) {
|
||||
testBinaryData += String.fromCharCode(i);
|
||||
}
|
||||
while (testBinaryData.length < minFileSize) {
|
||||
testBinaryData = testBinaryData + testBinaryData;
|
||||
}
|
||||
|
||||
var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
|
||||
testBinaryData.length % 3);
|
||||
var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
|
||||
testBinaryData.length % 3);
|
||||
var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
|
||||
testBinaryData.length % 3);
|
||||
|
||||
|
||||
//Set up files for testing
|
||||
var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
|
||||
var opener = SpecialPowers.loadChromeScript(openerURL);
|
||||
opener.addMessageListener("files.opened", onFilesOpened);
|
||||
opener.sendAsyncMessage("files.open", [
|
||||
testASCIIData,
|
||||
testBinaryData,
|
||||
null,
|
||||
convertToUTF8(testTextData),
|
||||
convertToUTF16(testTextData),
|
||||
"",
|
||||
dataurldata0,
|
||||
dataurldata1,
|
||||
dataurldata2,
|
||||
]);
|
||||
|
||||
function onFilesOpened(message) {
|
||||
let [
|
||||
asciiFile,
|
||||
binaryFile,
|
||||
nonExistingFile,
|
||||
utf8TextFile,
|
||||
utf16TextFile,
|
||||
emptyFile,
|
||||
dataUrlFile0,
|
||||
dataUrlFile1,
|
||||
dataUrlFile2,
|
||||
] = message;
|
||||
|
||||
// Test that plain reading works and fires events as expected, both
|
||||
// for text and binary reading
|
||||
|
||||
var onloadHasRunText = false;
|
||||
var onloadStartHasRunText = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
|
||||
r.addEventListener("load", function() { onloadHasRunText = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunText = true });
|
||||
r.readAsText(asciiFile);
|
||||
is(r.readyState, FileReader.LOADING, "correct loading text readyState");
|
||||
is(onloadHasRunText, false, "text loading must be async");
|
||||
is(onloadStartHasRunText, true, "text loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
var onloadHasRunBinary = false;
|
||||
var onloadStartHasRunBinary = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
|
||||
r.addEventListener("load", function() { onloadHasRunBinary = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true });
|
||||
r.readAsBinaryString(binaryFile);
|
||||
r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
|
||||
is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
|
||||
is(onloadHasRunBinary, false, "binary loading must be async");
|
||||
is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
var onloadHasRunArrayBuffer = false;
|
||||
var onloadStartHasRunArrayBuffer = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
|
||||
r.addEventListener("load", function() { onloadHasRunArrayBuffer = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true });
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
|
||||
is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
|
||||
is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
|
||||
is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
// Test a variety of encodings, and make sure they work properly
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
|
||||
r.readAsText(asciiFile, "");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
|
||||
r.readAsText(asciiFile, "iso-8859-1");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testTextData,
|
||||
convertToUTF8(testTextData).length,
|
||||
"utf8 reading");
|
||||
r.readAsText(utf8TextFile, "utf8");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.readAsText(utf16TextFile, "utf-16");
|
||||
r.onload = getLoadHandler(testTextData,
|
||||
convertToUTF16(testTextData).length,
|
||||
"utf16 reading");
|
||||
expectedTestCount++;
|
||||
|
||||
// Test get result without reading
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY,
|
||||
"readyState in test reader get result without reading");
|
||||
is(r.error, null,
|
||||
"no error in test reader get result without reading");
|
||||
is(r.result, null,
|
||||
"result in test reader get result without reading");
|
||||
|
||||
// Test loading an empty file works (and doesn't crash!)
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty no encoding reading");
|
||||
r.readAsText(emptyFile, "");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty utf8 reading");
|
||||
r.readAsText(emptyFile, "utf8");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty utf16 reading");
|
||||
r.readAsText(emptyFile, "utf-16");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty binary string reading");
|
||||
r.readAsBinaryString(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
|
||||
r.readAsArrayBuffer(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(""), 0, "empt binary string reading");
|
||||
r.readAsDataURL(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
// Test reusing a FileReader to read multiple times
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData,
|
||||
testASCIIData.length,
|
||||
"to-be-reused reading text")
|
||||
var makeAnotherReadListener = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener);
|
||||
r.onload = getLoadHandler(testASCIIData,
|
||||
testASCIIData.length,
|
||||
"reused reading text");
|
||||
r.readAsText(asciiFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener);
|
||||
r.readAsText(asciiFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading binary")
|
||||
var makeAnotherReadListener2 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener2);
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading binary");
|
||||
r.readAsBinaryString(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener2);
|
||||
r.readAsBinaryString(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(testBinaryData),
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading data url")
|
||||
var makeAnotherReadListener3 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener3);
|
||||
r.onload = getLoadHandler(convertToDataURL(testBinaryData),
|
||||
testBinaryData.length,
|
||||
"reused reading data url");
|
||||
r.readAsDataURL(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener3);
|
||||
r.readAsDataURL(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading arrayBuffer")
|
||||
var makeAnotherReadListener4 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener4);
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading arrayBuffer");
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener4);
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
// Test first reading as ArrayBuffer then read as something else
|
||||
// (BinaryString) and doesn't crash
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading arrayBuffer")
|
||||
var makeAnotherReadListener5 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener5);
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading binary string");
|
||||
r.readAsBinaryString(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener5);
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
//Test data-URI encoding on differing file sizes
|
||||
is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata0),
|
||||
dataurldata0.length,
|
||||
"dataurl reading, %3 = 0");
|
||||
r.readAsDataURL(dataUrlFile0);
|
||||
expectedTestCount++;
|
||||
|
||||
is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata1),
|
||||
dataurldata1.length,
|
||||
"dataurl reading, %3 = 1");
|
||||
r.readAsDataURL(dataUrlFile1);
|
||||
expectedTestCount++;
|
||||
|
||||
is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata2),
|
||||
dataurldata2.length,
|
||||
"dataurl reading, %3 = 2");
|
||||
r.readAsDataURL(dataUrlFile2),
|
||||
expectedTestCount++;
|
||||
|
||||
|
||||
// Test abort()
|
||||
var abortHasRun = false;
|
||||
var loadEndHasRun = false;
|
||||
r = new FileReader();
|
||||
r.onabort = function (event) {
|
||||
is(abortHasRun, false, "abort should only fire once");
|
||||
is(loadEndHasRun, false, "loadend shouldn't have fired yet");
|
||||
abortHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onloadend = function (event) {
|
||||
is(abortHasRun, true, "abort should fire before loadend");
|
||||
is(loadEndHasRun, false, "loadend should only fire once");
|
||||
loadEndHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onload = function() { ok(false, "load should not fire for aborted reads") };
|
||||
r.onerror = function() { ok(false, "error should not fire for aborted reads") };
|
||||
r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
|
||||
var abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
is(abortThrew, false, "abort() doesn't throw");
|
||||
is(abortHasRun, false, "abort() is a no-op unless loading");
|
||||
r.readAsText(asciiFile);
|
||||
r.abort();
|
||||
is(abortHasRun, true, "abort should fire sync");
|
||||
is(loadEndHasRun, true, "loadend should fire sync");
|
||||
|
||||
// Test calling readAsX to cause abort()
|
||||
var reuseAbortHasRun = false;
|
||||
r = new FileReader();
|
||||
r.onabort = function (event) {
|
||||
is(reuseAbortHasRun, false, "abort should only fire once");
|
||||
reuseAbortHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onload = function() { ok(false, "load should fire for nested reads"); };
|
||||
|
||||
var abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
is(abortThrew, false, "abort() should not throw");
|
||||
is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
|
||||
r.readAsText(asciiFile);
|
||||
|
||||
var readThrew = false;
|
||||
try {
|
||||
r.readAsText(asciiFile);
|
||||
} catch(e) {
|
||||
readThrew = true;
|
||||
}
|
||||
is(readThrew, true, "readAsText() must throw if loading");
|
||||
is(reuseAbortHasRun, false, "abort should not fire");
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
|
||||
expectedTestCount++;
|
||||
|
||||
|
||||
// Test reading from nonexistent files
|
||||
r = new FileReader();
|
||||
var didThrow = false;
|
||||
r.onerror = function (event) {
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
|
||||
is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
testHasRun();
|
||||
};
|
||||
r.onload = function (event) {
|
||||
is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
|
||||
testHasRun();
|
||||
};
|
||||
try {
|
||||
r.readAsDataURL(nonExistingFile);
|
||||
expectedTestCount++;
|
||||
} catch(ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
// Once this test passes, we should test that onerror gets called and
|
||||
// that the FileReader object is in the right state during that call.
|
||||
is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
|
||||
|
||||
|
||||
function getLoadHandler(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"readyState in test " + testName);
|
||||
is(event.target.error, null,
|
||||
"no error in test " + testName);
|
||||
is(event.target.result, expectedResult,
|
||||
"result in test " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"loaded in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"total in test " + testName);
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"readyState in test " + testName);
|
||||
is(event.target.error, null,
|
||||
"no error in test " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"loaded in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"total in test " + testName);
|
||||
is(event.target.result.byteLength, expectedLength,
|
||||
"array buffer size in test " + testName);
|
||||
var u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), expectedResult,
|
||||
"array buffer contents in test " + testName);
|
||||
u8v = null;
|
||||
SpecialPowers.gc();
|
||||
is(event.target.result.byteLength, expectedLength,
|
||||
"array buffer size after gc in test " + testName);
|
||||
u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), expectedResult,
|
||||
"array buffer contents after gc in test " + testName);
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function testHasRun() {
|
||||
//alert(testRanCounter);
|
||||
++testRanCounter;
|
||||
if (testRanCounter == expectedTestCount) {
|
||||
is(testSetupFinished, true, "test setup should have finished; check for exceptions");
|
||||
is(onloadHasRunText, true, "onload text should have fired by now");
|
||||
is(onloadHasRunBinary, true, "onload binary should have fired by now");
|
||||
opener.destroy();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
testSetupFinished = true;
|
||||
}
|
||||
|
||||
function convertToUTF16(s) {
|
||||
res = "";
|
||||
for (var i = 0; i < s.length; ++i) {
|
||||
c = s.charCodeAt(i);
|
||||
res += String.fromCharCode(c & 255, c >>> 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function convertToUTF8(s) {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
function convertToDataURL(s) {
|
||||
return "data:application/octet-stream;base64," + btoa(s);
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body> </html>
|
24
dom/file/tests/test_fileapi_basic.html
Normal file
24
dom/file/tests/test_fileapi_basic.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
return runBasicTests(data);
|
||||
})
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
38
dom/file/tests/test_fileapi_basic_worker.html
Normal file
38
dom/file/tests/test_fileapi_basic_worker.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
let worker = new Worker('worker_fileReader.js');
|
||||
worker.postMessage({ tests: 'basic', data });
|
||||
|
||||
worker.onmessage = event => {
|
||||
if (event.data.type == 'finish') {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type == 'check') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "Unknown message.");
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
24
dom/file/tests/test_fileapi_encoding.html
Normal file
24
dom/file/tests/test_fileapi_encoding.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
return runEncodingTests(data);
|
||||
})
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
38
dom/file/tests/test_fileapi_encoding_worker.html
Normal file
38
dom/file/tests/test_fileapi_encoding_worker.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
let worker = new Worker('worker_fileReader.js');
|
||||
worker.postMessage({ tests: 'encoding', data });
|
||||
|
||||
worker.onmessage = event => {
|
||||
if (event.data.type == 'finish') {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type == 'check') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "Unknown message.");
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
24
dom/file/tests/test_fileapi_other.html
Normal file
24
dom/file/tests/test_fileapi_other.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
return runOtherTests(data);
|
||||
})
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
38
dom/file/tests/test_fileapi_other_worker.html
Normal file
38
dom/file/tests/test_fileapi_other_worker.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
let worker = new Worker('worker_fileReader.js');
|
||||
worker.postMessage({ tests: 'other', data });
|
||||
|
||||
worker.onmessage = event => {
|
||||
if (event.data.type == 'finish') {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type == 'check') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "Unknown message.");
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,167 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=575946
|
||||
-->
|
||||
<title>Test for Bug 575946</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="fileutils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=575946">Mozilla Bug 575946</a>
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
<canvas id=testcanvas hidden moz-opaque></canvas>
|
||||
<input id="fileList" type="file"></input>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
var fileNum = 1;
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Create files containing data we'll test with. We'll want long
|
||||
// strings to ensure they span multiple buffers while loading
|
||||
|
||||
// Create a decent-sized image
|
||||
cx = $("canvas").getContext('2d');
|
||||
var s = cx.canvas.width;
|
||||
var grad = cx.createLinearGradient(0, 0, s-1, s-1);
|
||||
for (i = 0; i < 0.95; i += .1) {
|
||||
grad.addColorStop(i, "white");
|
||||
grad.addColorStop(i + .05, "black");
|
||||
}
|
||||
grad.addColorStop(1, "white");
|
||||
cx.fillStyle = grad;
|
||||
cx.fillRect(0, 0, s-1, s-1);
|
||||
cx.fillStyle = "rgba(200, 0, 0, 0.9)";
|
||||
cx.fillRect(.1 * s, .1 * s, .7 * s, .7 * s);
|
||||
cx.strokeStyle = "rgba(0, 0, 130, 0.5)";
|
||||
cx.lineWidth = .14 * s;
|
||||
cx.beginPath();
|
||||
cx.arc(.6 * s, .6 * s, .3 * s, 0, Math.PI*2, true);
|
||||
cx.stroke();
|
||||
cx.closePath();
|
||||
cx.fillStyle = "rgb(0, 255, 0)";
|
||||
cx.beginPath();
|
||||
cx.arc(.1 * s, .8 * s, .1 * s, 0, Math.PI*2, true);
|
||||
cx.fill();
|
||||
cx.closePath();
|
||||
|
||||
|
||||
function imageLoadHandler(event) {
|
||||
var origcanvas = $("canvas");
|
||||
var testcanvas = $("testcanvas");
|
||||
var image = event.target;
|
||||
is(image.naturalWidth, origcanvas.width, "width correct");
|
||||
is(image.naturalHeight, origcanvas.height, "height correct");
|
||||
|
||||
testcanvas.width = origcanvas.width;
|
||||
testcanvas.height = origcanvas.height;
|
||||
testcanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
// Do not use |is(testcanvas.toDataURL("image/png"), origcanvas.toDataURL("image/png"), "...");| that results in a _very_ long line.
|
||||
var origDataURL = origcanvas.toDataURL("image/png");
|
||||
var testDataURL = testcanvas.toDataURL("image/png");
|
||||
is(testDataURL.length, origDataURL.length,
|
||||
"Length of correct image data");
|
||||
ok(testDataURL == origDataURL,
|
||||
"Content of correct image data");
|
||||
|
||||
testHasRun();
|
||||
}
|
||||
|
||||
var fileData =
|
||||
atob(cx.canvas.toDataURL("image/png").substring("data:text/png;base64,".length + 1));
|
||||
var size = fileData.length;
|
||||
var testBinaryData = "";
|
||||
|
||||
// This might fail if we dramatically improve the png encoder. If that happens
|
||||
// please increase the complexity or size of the image generated above to ensure
|
||||
// that we're testing with files that are large enough.
|
||||
ok(size > 65536, "test data sufficiently large");
|
||||
|
||||
SpecialPowers.createFiles([{name: "basicTestFile", data: fileData}],
|
||||
basicTest);
|
||||
|
||||
function basicTest(files) {
|
||||
var fileFile = files[0];
|
||||
|
||||
// Test that basic properties work
|
||||
var memFile = cx.canvas.mozGetAsFile("image/png");
|
||||
testSlice(memFile, size, "image/png", fileData, "memFile");
|
||||
testSlice(fileFile, size, "", fileData, "fileFile");
|
||||
|
||||
// Try loading directly from slice into an image
|
||||
for (var i = 0; i < 256; i++) {
|
||||
testBinaryData += String.fromCharCode(i);
|
||||
}
|
||||
while (testBinaryData.length < 20000) {
|
||||
testBinaryData += testBinaryData;
|
||||
}
|
||||
|
||||
// image in the middle
|
||||
SpecialPowers.createFiles([{name: "middleTestFile",
|
||||
data: testBinaryData + fileData + testBinaryData}],
|
||||
imageMiddleTest);
|
||||
}
|
||||
|
||||
function imageMiddleTest(files) {
|
||||
var imgfile = files[0];
|
||||
is(imgfile.size, size + testBinaryData.length * 2, "correct file size (middle)");
|
||||
var img = new Image;
|
||||
img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size));
|
||||
img.onload = imageLoadHandler;
|
||||
expectedTestCount++;
|
||||
|
||||
// image at start
|
||||
SpecialPowers.createFiles([{name: "startTestFile",
|
||||
data: fileData + testBinaryData}],
|
||||
imageStartTest);
|
||||
}
|
||||
|
||||
function imageStartTest(files) {
|
||||
var imgfile = files[0];
|
||||
is(imgfile.size, size + testBinaryData.length, "correct file size (start)");
|
||||
var img = new Image;
|
||||
img.src = URL.createObjectURL(imgfile.slice(0, size));
|
||||
img.onload = imageLoadHandler;
|
||||
expectedTestCount++;
|
||||
|
||||
// image at end
|
||||
SpecialPowers.createFiles([{name: "endTestFile",
|
||||
data: testBinaryData + fileData}],
|
||||
imageEndTest);
|
||||
}
|
||||
|
||||
function imageEndTest(files) {
|
||||
var imgfile = files[0];
|
||||
is(imgfile.size, size + testBinaryData.length, "correct file size (end)");
|
||||
var img = new Image;
|
||||
img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size));
|
||||
img.onload = imageLoadHandler;
|
||||
expectedTestCount++;
|
||||
|
||||
// image past end
|
||||
SpecialPowers.createFiles([{name: "pastEndTestFile",
|
||||
data: testBinaryData + fileData}],
|
||||
imagePastEndTest);
|
||||
}
|
||||
|
||||
function imagePastEndTest(files) {
|
||||
var imgfile = files[0];
|
||||
is(imgfile.size, size + testBinaryData.length, "correct file size (past end)");
|
||||
var img = new Image;
|
||||
img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size + 1000));
|
||||
img.onload = imageLoadHandler;
|
||||
expectedTestCount++;
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
139
dom/file/tests/test_fileapi_slice_image.html
Normal file
139
dom/file/tests/test_fileapi_slice_image.html
Normal file
@ -0,0 +1,139 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for File API + Slice</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
<canvas id=testcanvas hidden moz-opaque></canvas>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(4);
|
||||
|
||||
// Create files containing data we'll test with. We'll want long
|
||||
// strings to ensure they span multiple buffers while loading
|
||||
|
||||
let canvasData;
|
||||
let testBinaryData;
|
||||
|
||||
function imageLoadHandler(event, resolve) {
|
||||
let origcanvas = $("canvas");
|
||||
let testcanvas = $("testcanvas");
|
||||
let image = event.target;
|
||||
is(image.naturalWidth, origcanvas.width, "width correct");
|
||||
is(image.naturalHeight, origcanvas.height, "height correct");
|
||||
|
||||
testcanvas.width = origcanvas.width;
|
||||
testcanvas.height = origcanvas.height;
|
||||
testcanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
// Do not use |is(testcanvas.toDataURL("image/png"), origcanvas.toDataURL("image/png"), "...");| that results in a _very_ long line.
|
||||
let origDataURL = origcanvas.toDataURL("image/png");
|
||||
let testDataURL = testcanvas.toDataURL("image/png");
|
||||
is(testDataURL.length, origDataURL.length,
|
||||
"Length of correct image data");
|
||||
ok(testDataURL == origDataURL,
|
||||
"Content of correct image data");
|
||||
resolve();
|
||||
}
|
||||
|
||||
createCanvasURL()
|
||||
.then(data => {
|
||||
for (var i = 0; i < 256; i++) {
|
||||
testBinaryData += String.fromCharCode(i);
|
||||
}
|
||||
while (testBinaryData.length < 20000) {
|
||||
testBinaryData += testBinaryData;
|
||||
}
|
||||
|
||||
canvasData = data;
|
||||
})
|
||||
|
||||
// image in the middle
|
||||
.then(() => {
|
||||
return createFile(testBinaryData + canvasData + testBinaryData, "middleTestFile");
|
||||
})
|
||||
|
||||
// image in the middle - loader
|
||||
.then(file => {
|
||||
return new Promise(resolve => {
|
||||
is(file.size, canvasData.length + testBinaryData.length * 2, "correct file size (middle)");
|
||||
|
||||
var img = new Image();
|
||||
img.src = URL.createObjectURL(file.slice(testBinaryData.length,
|
||||
testBinaryData.length + canvasData.length));
|
||||
img.onload = event => {
|
||||
imageLoadHandler(event, resolve);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// image at start
|
||||
.then(() => {
|
||||
return createFile(canvasData + testBinaryData, "startTestFile");
|
||||
})
|
||||
|
||||
// image at start - loader
|
||||
.then(file => {
|
||||
return new Promise(resolve => {
|
||||
is(file.size, canvasData.length + testBinaryData.length, "correct file size (start)");
|
||||
|
||||
var img = new Image();
|
||||
img.src = URL.createObjectURL(file.slice(0, canvasData.length));
|
||||
img.onload = event => {
|
||||
imageLoadHandler(event, resolve);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// image at end
|
||||
.then(() => {
|
||||
return createFile(testBinaryData + canvasData, "endTestFile");
|
||||
})
|
||||
|
||||
// image at end - loader
|
||||
.then(file => {
|
||||
return new Promise(resolve => {
|
||||
is(file.size, canvasData.length + testBinaryData.length, "correct file size (end)");
|
||||
|
||||
var img = new Image();
|
||||
img.src = URL.createObjectURL(file.slice(testBinaryData.length,
|
||||
testBinaryData.length + canvasData.length));
|
||||
img.onload = event => {
|
||||
imageLoadHandler(event, resolve);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// image past end
|
||||
.then(() => {
|
||||
return createFile(testBinaryData + canvasData, "pastEndTestFile");
|
||||
})
|
||||
|
||||
// image past end - loader
|
||||
.then(file => {
|
||||
return new Promise(resolve => {
|
||||
is(file.size, canvasData.length + testBinaryData.length, "correct file size (end)");
|
||||
|
||||
var img = new Image();
|
||||
img.src = URL.createObjectURL(file.slice(testBinaryData.length,
|
||||
testBinaryData.length + canvasData.length + 1000));
|
||||
img.onload = event => {
|
||||
imageLoadHandler(event, resolve);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
32
dom/file/tests/test_fileapi_slice_memFile_1.html
Normal file
32
dom/file/tests/test_fileapi_slice_memFile_1.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for File API + Slice (in memory)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(4);
|
||||
|
||||
createCanvasURL()
|
||||
.then(data => {
|
||||
let cx = $("canvas").getContext('2d');
|
||||
let memFile = cx.canvas.mozGetAsFile("image/png");
|
||||
return testSlice(memFile, data.length, "image/png", data, "memFile", RANGE_1);
|
||||
})
|
||||
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
32
dom/file/tests/test_fileapi_slice_memFile_2.html
Normal file
32
dom/file/tests/test_fileapi_slice_memFile_2.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for File API + Slice (in memory)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(4);
|
||||
|
||||
createCanvasURL()
|
||||
.then(data => {
|
||||
let cx = $("canvas").getContext('2d');
|
||||
let memFile = cx.canvas.mozGetAsFile("image/png");
|
||||
return testSlice(memFile, data.length, "image/png", data, "memFile", RANGE_2);
|
||||
})
|
||||
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
37
dom/file/tests/test_fileapi_slice_realFile_1.html
Normal file
37
dom/file/tests/test_fileapi_slice_realFile_1.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for File API + Slice (in file)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(4);
|
||||
|
||||
let canvasData;
|
||||
|
||||
createCanvasURL()
|
||||
.then(data => {
|
||||
canvasData = data;
|
||||
return createFile(data, "basicTestFile1");
|
||||
})
|
||||
|
||||
.then(file => {
|
||||
return testSlice(file, canvasData.length, "", canvasData, "fileFile", RANGE_1);
|
||||
})
|
||||
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
37
dom/file/tests/test_fileapi_slice_realFile_2.html
Normal file
37
dom/file/tests/test_fileapi_slice_realFile_2.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for File API + Slice (in file)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="common_blob.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p id="display">
|
||||
<canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(4);
|
||||
|
||||
let canvasData;
|
||||
|
||||
createCanvasURL()
|
||||
.then(data => {
|
||||
canvasData = data;
|
||||
return createFile(data, "basicTestFile2");
|
||||
})
|
||||
|
||||
.then(file => {
|
||||
return testSlice(file, canvasData.length, "", canvasData, "fileFile", RANGE_2);
|
||||
})
|
||||
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
24
dom/file/tests/test_fileapi_twice.html
Normal file
24
dom/file/tests/test_fileapi_twice.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
return runTwiceTests(data);
|
||||
})
|
||||
.then(SimpleTest.finish);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
38
dom/file/tests/test_fileapi_twice_worker.html
Normal file
38
dom/file/tests/test_fileapi_twice_worker.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader API in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="common_fileReader.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(3);
|
||||
|
||||
test_setup()
|
||||
.then(data => {
|
||||
let worker = new Worker('worker_fileReader.js');
|
||||
worker.postMessage({ tests: 'twice', data });
|
||||
|
||||
worker.onmessage = event => {
|
||||
if (event.data.type == 'finish') {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type == 'check') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "Unknown message.");
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -4,7 +4,6 @@
|
||||
<title>Test blob URL for non-ascii domain</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="fileutils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
@ -25,6 +24,5 @@ document.getElementById('content').appendChild(iframe);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
30
dom/file/tests/worker_fileReader.js
Normal file
30
dom/file/tests/worker_fileReader.js
Normal file
@ -0,0 +1,30 @@
|
||||
importScripts('common_fileReader.js');
|
||||
|
||||
function ok(a, msg) {
|
||||
postMessage({type:'check', msg, status: !!a});
|
||||
}
|
||||
|
||||
function is(a, b, msg) {
|
||||
ok(a === b, msg);
|
||||
}
|
||||
|
||||
onmessage = event => {
|
||||
let p;
|
||||
|
||||
if (event.data.tests == 'basic') {
|
||||
p = runBasicTests(event.data.data);
|
||||
} else if (event.data.tests == 'encoding') {
|
||||
p = runEncodingTests(event.data.data);
|
||||
} else if (event.data.tests == 'twice') {
|
||||
p = runTwiceTests(event.data.data);
|
||||
} else if (event.data.tests == 'other') {
|
||||
p = runOtherTests(event.data.data);
|
||||
} else {
|
||||
postMessage({type: 'error'});
|
||||
return;
|
||||
}
|
||||
|
||||
p.then(() => {
|
||||
postMessage({ type: 'finish' });
|
||||
});
|
||||
};
|
@ -174,7 +174,7 @@ TrackUnionStream::TrackUnionStream()
|
||||
TrackID id;
|
||||
if (IsTrackIDExplicit(id = aPort->GetDestinationTrackId())) {
|
||||
MOZ_ASSERT(id >= mNextAvailableTrackID &&
|
||||
mUsedTracks.BinaryIndexOf(id) == mUsedTracks.NoIndex,
|
||||
!mUsedTracks.ContainsSorted(id),
|
||||
"Desired destination id taken. Only provide a destination ID "
|
||||
"if you can assure its availability, or we may not be able "
|
||||
"to bind to the correct DOM-side track.");
|
||||
@ -191,7 +191,7 @@ TrackUnionStream::TrackUnionStream()
|
||||
mUsedTracks.InsertElementSorted(id);
|
||||
} else if ((id = aTrack->GetID()) &&
|
||||
id > mNextAvailableTrackID &&
|
||||
mUsedTracks.BinaryIndexOf(id) == mUsedTracks.NoIndex) {
|
||||
!mUsedTracks.ContainsSorted(id)) {
|
||||
// Input id available. Mark it used in mUsedTracks.
|
||||
mUsedTracks.InsertElementSorted(id);
|
||||
} else {
|
||||
|
@ -111,6 +111,8 @@ MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
|
||||
mVoiceEngine(nullptr),
|
||||
mAudioInput(nullptr),
|
||||
mFullDuplex(aPrefs.mFullDuplex),
|
||||
mDelayAgnostic(aPrefs.mDelayAgnostic),
|
||||
mExtendedFilter(aPrefs.mExtendedFilter),
|
||||
mHasTabVideoSource(false)
|
||||
{
|
||||
nsCOMPtr<nsIComponentRegistrar> compMgr;
|
||||
@ -294,7 +296,7 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
|
||||
#endif
|
||||
|
||||
if (!mVoiceEngine) {
|
||||
mVoiceEngine = webrtc::VoiceEngine::Create(/*mConfig*/);
|
||||
mVoiceEngine = webrtc::VoiceEngine::Create();
|
||||
if (!mVoiceEngine) {
|
||||
return;
|
||||
}
|
||||
@ -367,7 +369,8 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
|
||||
audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i);
|
||||
}
|
||||
aSource = new MediaEngineWebRTCMicrophoneSource(mVoiceEngine, audioinput,
|
||||
i, deviceName, uniqueId);
|
||||
i, deviceName, uniqueId,
|
||||
mDelayAgnostic, mExtendedFilter);
|
||||
mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
|
||||
aASources->AppendElement(aSource);
|
||||
}
|
||||
|
@ -499,7 +499,9 @@ public:
|
||||
mozilla::AudioInput* aAudioInput,
|
||||
int aIndex,
|
||||
const char* name,
|
||||
const char* uuid);
|
||||
const char* uuid,
|
||||
bool aDelayAgnostic,
|
||||
bool aExtendedFilter);
|
||||
|
||||
void GetName(nsAString& aName) const override;
|
||||
void GetUUID(nsACString& aUUID) const override;
|
||||
@ -621,6 +623,8 @@ private:
|
||||
|
||||
int mCapIndex;
|
||||
int mChannel;
|
||||
bool mDelayAgnostic;
|
||||
bool mExtendedFilter;
|
||||
MOZ_INIT_OUTSIDE_CTOR TrackID mTrackID;
|
||||
bool mStarted;
|
||||
|
||||
@ -675,6 +679,8 @@ private:
|
||||
webrtc::VoiceEngine* mVoiceEngine;
|
||||
RefPtr<mozilla::AudioInput> mAudioInput;
|
||||
bool mFullDuplex;
|
||||
bool mDelayAgnostic;
|
||||
bool mExtendedFilter;
|
||||
bool mHasTabVideoSource;
|
||||
|
||||
// Store devices we've already seen in a hashtable for quick return.
|
||||
|
@ -191,13 +191,17 @@ MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
|
||||
mozilla::AudioInput* aAudioInput,
|
||||
int aIndex,
|
||||
const char* name,
|
||||
const char* uuid)
|
||||
const char* uuid,
|
||||
bool aDelayAgnostic,
|
||||
bool aExtendedFilter)
|
||||
: MediaEngineAudioSource(kReleased)
|
||||
, mVoiceEngine(aVoiceEnginePtr)
|
||||
, mAudioInput(aAudioInput)
|
||||
, mMonitor("WebRTCMic.Monitor")
|
||||
, mCapIndex(aIndex)
|
||||
, mChannel(-1)
|
||||
, mDelayAgnostic(aDelayAgnostic)
|
||||
, mExtendedFilter(aExtendedFilter)
|
||||
, mTrackID(TRACK_NONE)
|
||||
, mStarted(false)
|
||||
, mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
|
||||
@ -781,6 +785,10 @@ MediaEngineWebRTCMicrophoneSource::InitEngine()
|
||||
mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
|
||||
|
||||
mVoEBase->Init();
|
||||
webrtc::Config config;
|
||||
config.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(mExtendedFilter));
|
||||
config.Set<webrtc::DelayAgnostic>(new webrtc::DelayAgnostic(mDelayAgnostic));
|
||||
mVoEBase->audio_processing()->SetExtraOptions(config);
|
||||
|
||||
mVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
|
||||
if (mVoERender) {
|
||||
|
@ -71,7 +71,6 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel)
|
||||
{
|
||||
if (aChannel) {
|
||||
aChannel->GetAsyncOpen(&mAsyncOpen);
|
||||
aChannel->GetDispatchFetchEventStart(&mWorkerStart);
|
||||
aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin);
|
||||
aChannel->GetRedirectCount(&mRedirectCount);
|
||||
aChannel->GetRedirectStart(&mRedirectStart);
|
||||
@ -87,6 +86,12 @@ PerformanceTiming::InitializeTimingInfo(nsITimedChannel* aChannel)
|
||||
aChannel->GetResponseEnd(&mResponseEnd);
|
||||
aChannel->GetCacheReadEnd(&mCacheReadEnd);
|
||||
|
||||
aChannel->GetDispatchFetchEventStart(&mWorkerStart);
|
||||
aChannel->GetHandleFetchEventStart(&mWorkerRequestStart);
|
||||
// TODO: Track when FetchEvent.respondWith() promise resolves as
|
||||
// ServiceWorker interception responseStart?
|
||||
aChannel->GetHandleFetchEventEnd(&mWorkerResponseEnd);
|
||||
|
||||
// The performance timing api essentially requires that the event timestamps
|
||||
// have a strict relation with each other. The truth, however, is the browser
|
||||
// engages in a number of speculative activities that sometimes mean connections
|
||||
@ -399,6 +404,11 @@ PerformanceTiming::RequestStartHighRes()
|
||||
nsContentUtils::ShouldResistFingerprinting()) {
|
||||
return mZeroTime;
|
||||
}
|
||||
|
||||
if (mRequestStart.IsNull()) {
|
||||
mRequestStart = mWorkerRequestStart;
|
||||
}
|
||||
|
||||
return TimeStampToDOMHighResOrFetchStart(mRequestStart);
|
||||
}
|
||||
|
||||
@ -444,6 +454,9 @@ PerformanceTiming::ResponseEndHighRes()
|
||||
(!mCacheReadEnd.IsNull() && mCacheReadEnd < mResponseEnd)) {
|
||||
mResponseEnd = mCacheReadEnd;
|
||||
}
|
||||
if (mResponseEnd.IsNull()) {
|
||||
mResponseEnd = mWorkerResponseEnd;
|
||||
}
|
||||
// Bug 1155008 - nsHttpTransaction is racy. Return ResponseStart when null
|
||||
return mResponseEnd.IsNull() ? ResponseStartHighRes()
|
||||
: TimeStampToDOMHighRes(mResponseEnd);
|
||||
|
@ -278,7 +278,6 @@ private:
|
||||
DOMHighResTimeStamp mZeroTime;
|
||||
|
||||
TimeStamp mAsyncOpen;
|
||||
TimeStamp mWorkerStart;
|
||||
TimeStamp mRedirectStart;
|
||||
TimeStamp mRedirectEnd;
|
||||
TimeStamp mDomainLookupStart;
|
||||
@ -291,6 +290,12 @@ private:
|
||||
TimeStamp mCacheReadStart;
|
||||
TimeStamp mResponseEnd;
|
||||
TimeStamp mCacheReadEnd;
|
||||
|
||||
// ServiceWorker interception timing information
|
||||
TimeStamp mWorkerStart;
|
||||
TimeStamp mWorkerRequestStart;
|
||||
TimeStamp mWorkerResponseEnd;
|
||||
|
||||
uint8_t mRedirectCount;
|
||||
bool mTimingAllowed;
|
||||
bool mAllRedirectsSameOrigin;
|
||||
|
@ -124,11 +124,30 @@ namespace {
|
||||
|
||||
const uint32_t kSQLitePageSizeOverride = 512;
|
||||
|
||||
|
||||
const uint32_t kHackyDowngradeMajorStorageVersion = 2;
|
||||
const uint32_t kHackyDowngradeMinorStorageVersion = 1;
|
||||
|
||||
// Important version history:
|
||||
// - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
|
||||
// which caused Firefox 57 release concerns because the major schema upgrade
|
||||
// means anyone downgrading to Firefox 56 will experience a non-operational
|
||||
// QuotaManager and all of its clients.
|
||||
// - Bug 1404344 got very concerned about that and so we decided to effectively
|
||||
// rename 3.0 to 2.1, effective in Firefox 57. This works because post
|
||||
// storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
|
||||
// increases. It also works because all the upgrade did was give the DOM
|
||||
// Cache API QuotaClient an opportunity to create its newly added .padding
|
||||
// files during initialization/upgrade, which isn't functionally necessary as
|
||||
// that can be done on demand.
|
||||
|
||||
// Major storage version. Bump for backwards-incompatible changes.
|
||||
const uint32_t kMajorStorageVersion = 3;
|
||||
// (The next major version should be 4 to distinguish from the Bug 1290481
|
||||
// downgrade snafu.)
|
||||
const uint32_t kMajorStorageVersion = 2;
|
||||
|
||||
// Minor storage version. Bump for backwards-compatible changes.
|
||||
const uint32_t kMinorStorageVersion = 0;
|
||||
const uint32_t kMinorStorageVersion = 1;
|
||||
|
||||
// The storage version we store in the SQLite database is a (signed) 32-bit
|
||||
// integer. The major version is left-shifted 16 bits so the max value is
|
||||
@ -141,6 +160,10 @@ static_assert(kMinorStorageVersion <= 0xFFFF,
|
||||
const int32_t kStorageVersion =
|
||||
int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
|
||||
|
||||
// See comments above about why these are a thing.
|
||||
const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
|
||||
const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
|
||||
|
||||
static_assert(
|
||||
static_cast<uint32_t>(StorageType::Persistent) ==
|
||||
static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
|
||||
@ -1817,11 +1840,11 @@ private:
|
||||
// XXXtt: The following class is duplicated from
|
||||
// UpgradeStorageFrom1_0To2_0Helper and it should be extracted out in
|
||||
// bug 1395102.
|
||||
class UpgradeStorageFrom2_0To3_0Helper final
|
||||
class UpgradeStorageFrom2_0To2_1Helper final
|
||||
: public StorageDirectoryHelper
|
||||
{
|
||||
public:
|
||||
UpgradeStorageFrom2_0To3_0Helper(nsIFile* aDirectory,
|
||||
UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory,
|
||||
bool aPersistent)
|
||||
: StorageDirectoryHelper(aDirectory, aPersistent)
|
||||
{ }
|
||||
@ -4813,7 +4836,7 @@ QuotaManager::UpgradeStorageFrom1_0To2_0(mozIStorageConnection* aConnection)
|
||||
}
|
||||
|
||||
nsresult
|
||||
QuotaManager::UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection)
|
||||
QuotaManager::UpgradeStorageFrom2_0To2_1(mozIStorageConnection* aConnection)
|
||||
{
|
||||
AssertIsOnIOThread();
|
||||
MOZ_ASSERT(aConnection);
|
||||
@ -4846,8 +4869,8 @@ QuotaManager::UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection)
|
||||
}
|
||||
|
||||
bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT;
|
||||
RefPtr<UpgradeStorageFrom2_0To3_0Helper> helper =
|
||||
new UpgradeStorageFrom2_0To3_0Helper(directory, persistent);
|
||||
RefPtr<UpgradeStorageFrom2_0To2_1Helper> helper =
|
||||
new UpgradeStorageFrom2_0To2_1Helper(directory, persistent);
|
||||
|
||||
rv = helper->DoUpgrade();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@ -4867,7 +4890,7 @@ QuotaManager::UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection)
|
||||
}
|
||||
#endif
|
||||
|
||||
rv = aConnection->SetSchemaVersion(MakeStorageVersion(3, 0));
|
||||
rv = aConnection->SetSchemaVersion(MakeStorageVersion(2, 1));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
@ -4950,6 +4973,17 @@ QuotaManager::EnsureStorageIsInitialized()
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Hacky downgrade logic!
|
||||
// If we see major.minor of 3.0, downgrade it to be 2.1.
|
||||
if (storageVersion == kHackyPreDowngradeStorageVersion) {
|
||||
storageVersion = kHackyPostDowngradeStorageVersion;
|
||||
rv = connection->SetSchemaVersion(storageVersion);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
MOZ_ASSERT(false, "Downgrade didn't take.");
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (GetMajorStorageVersion(storageVersion) > kMajorStorageVersion) {
|
||||
NS_WARNING("Unable to initialize storage, version is too high!");
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -5023,7 +5057,7 @@ QuotaManager::EnsureStorageIsInitialized()
|
||||
MOZ_ASSERT(storageVersion == kStorageVersion);
|
||||
} else {
|
||||
// This logic needs to change next time we change the storage!
|
||||
static_assert(kStorageVersion == int32_t((3 << 16) + 0),
|
||||
static_assert(kStorageVersion == int32_t((2 << 16) + 1),
|
||||
"Upgrade function needed due to storage version increase.");
|
||||
|
||||
while (storageVersion != kStorageVersion) {
|
||||
@ -5032,7 +5066,7 @@ QuotaManager::EnsureStorageIsInitialized()
|
||||
} else if (storageVersion == MakeStorageVersion(1, 0)) {
|
||||
rv = UpgradeStorageFrom1_0To2_0(connection);
|
||||
} else if (storageVersion == MakeStorageVersion(2, 0)) {
|
||||
rv = UpgradeStorageFrom2_0To3_0(connection);
|
||||
rv = UpgradeStorageFrom2_0To2_1(connection);
|
||||
} else {
|
||||
NS_WARNING("Unable to initialize storage, no upgrade path is "
|
||||
"available!");
|
||||
@ -9396,7 +9430,7 @@ UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
|
||||
}
|
||||
|
||||
nsresult
|
||||
UpgradeStorageFrom2_0To3_0Helper::DoUpgrade()
|
||||
UpgradeStorageFrom2_0To2_1Helper::DoUpgrade()
|
||||
{
|
||||
AssertIsOnIOThread();
|
||||
|
||||
@ -9499,7 +9533,7 @@ UpgradeStorageFrom2_0To3_0Helper::DoUpgrade()
|
||||
}
|
||||
|
||||
nsresult
|
||||
UpgradeStorageFrom2_0To3_0Helper::MaybeUpgradeClients(
|
||||
UpgradeStorageFrom2_0To2_1Helper::MaybeUpgradeClients(
|
||||
const OriginProps& aOriginProps)
|
||||
{
|
||||
AssertIsOnIOThread();
|
||||
@ -9559,7 +9593,7 @@ UpgradeStorageFrom2_0To3_0Helper::MaybeUpgradeClients(
|
||||
Client* client = quotaManager->GetClient(clientType);
|
||||
MOZ_ASSERT(client);
|
||||
|
||||
rv = client->UpgradeStorageFrom2_0To3_0(file);
|
||||
rv = client->UpgradeStorageFrom2_0To2_1(file);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
@ -9572,7 +9606,7 @@ UpgradeStorageFrom2_0To3_0Helper::MaybeUpgradeClients(
|
||||
}
|
||||
|
||||
nsresult
|
||||
UpgradeStorageFrom2_0To3_0Helper::ProcessOriginDirectory(
|
||||
UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
|
||||
const OriginProps& aOriginProps)
|
||||
{
|
||||
AssertIsOnIOThread();
|
||||
|
@ -99,7 +99,7 @@ public:
|
||||
}
|
||||
|
||||
virtual nsresult
|
||||
UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory)
|
||||
UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -477,7 +477,7 @@ private:
|
||||
UpgradeStorageFrom1_0To2_0(mozIStorageConnection* aConnection);
|
||||
|
||||
nsresult
|
||||
UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection);
|
||||
UpgradeStorageFrom2_0To2_1(mozIStorageConnection* aConnection);
|
||||
|
||||
nsresult
|
||||
InitializeRepository(PersistenceType aPersistenceType);
|
||||
|
@ -30,7 +30,7 @@ function* testSteps()
|
||||
// 1. Remove the folder "storage/temporary".
|
||||
// 2. Copy the content under the "storage/default/chrome" to
|
||||
// "storage/default/http+++www.mozilla.org".
|
||||
installPackage("version3_0upgrade_profile");
|
||||
installPackage("version2_1upgrade_profile");
|
||||
|
||||
info("Checking padding file before upgrade (QM version 2.0)");
|
||||
|
||||
@ -43,13 +43,13 @@ function* testSteps()
|
||||
|
||||
info("Initializing");
|
||||
|
||||
// Initialize QuotaManager to trigger upgrade the QM to version 3.0
|
||||
// Initialize QuotaManager to trigger upgrade the QM to version 2.1
|
||||
let request = init(continueToNextStepSync);
|
||||
yield undefined;
|
||||
|
||||
ok(request.resultCode == NS_OK, "Initialization succeeded");
|
||||
|
||||
info("Checking padding files after upgrade (QM version 3.0)");
|
||||
info("Checking padding files after upgrade (QM version 2.1)");
|
||||
|
||||
for (let origin of origins) {
|
||||
let paddingFile = getRelativeFile(origin + paddingFilePath);
|
@ -16,7 +16,7 @@ support-files =
|
||||
removeAppsUpgrade_profile.zip
|
||||
storagePersistentUpgrade_profile.zip
|
||||
tempMetadataCleanup_profile.zip
|
||||
version3_0upgrade_profile.zip
|
||||
version2_1upgrade_profile.zip
|
||||
|
||||
[test_basics.js]
|
||||
[test_bad_origin_directory.js]
|
||||
@ -32,4 +32,4 @@ skip-if = release_or_beta
|
||||
[test_storagePersistentUpgrade.js]
|
||||
[test_tempMetadataCleanup.js]
|
||||
[test_unknownFiles.js]
|
||||
[test_version3_0upgrade.js]
|
||||
[test_version2_1upgrade.js]
|
||||
|
2
dom/smil/crashtests/1322849-1.svg
Normal file
2
dom/smil/crashtests/1322849-1.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<svg>
|
||||
<set fill='freeze' dur='8' repeatCount='1844674737095516'>
|
After Width: | Height: | Size: 65 B |
@ -52,5 +52,6 @@ load 699325-1.svg
|
||||
load 709907-1.svg
|
||||
load 720103-1.svg
|
||||
load 1010681-1.svg
|
||||
load 1322849-1.svg
|
||||
load 1375596-1.svg
|
||||
load 1402547-1.html
|
||||
|
@ -6,7 +6,8 @@
|
||||
|
||||
#include "nsSMILTimeValue.h"
|
||||
|
||||
const nsSMILTime nsSMILTimeValue::kUnresolvedMillis = INT64_MAX;
|
||||
const nsSMILTime nsSMILTimeValue::kUnresolvedMillis =
|
||||
std::numeric_limits<nsSMILTime>::max();
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// nsSMILTimeValue methods:
|
||||
|
@ -230,7 +230,8 @@ const nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = {
|
||||
{nullptr, 0}
|
||||
};
|
||||
|
||||
const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(INT64_MAX, false);
|
||||
const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(
|
||||
std::numeric_limits<nsSMILTime>::max(), false);
|
||||
|
||||
// The thresholds at which point we start filtering intervals and instance times
|
||||
// indiscriminately.
|
||||
@ -1927,8 +1928,11 @@ nsSMILTimedElement::GetRepeatDuration() const
|
||||
{
|
||||
nsSMILTimeValue multipliedDuration;
|
||||
if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
|
||||
multipliedDuration.SetMillis(
|
||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis())));
|
||||
if (mRepeatCount * double(mSimpleDur.GetMillis()) <=
|
||||
std::numeric_limits<nsSMILTime>::max()) {
|
||||
multipliedDuration.SetMillis(
|
||||
nsSMILTime(mRepeatCount * mSimpleDur.GetMillis()));
|
||||
}
|
||||
} else {
|
||||
multipliedDuration.SetIndefinite();
|
||||
}
|
||||
@ -2207,13 +2211,13 @@ nsresult
|
||||
nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
|
||||
double aOffsetSeconds, bool aIsBegin)
|
||||
{
|
||||
double offset = aOffsetSeconds * PR_MSEC_PER_SEC;
|
||||
double offset = NS_round(aOffsetSeconds * PR_MSEC_PER_SEC);
|
||||
|
||||
// Check we won't overflow the range of nsSMILTime
|
||||
if (aCurrentTime + NS_round(offset) > INT64_MAX)
|
||||
if (aCurrentTime + offset > std::numeric_limits<nsSMILTime>::max())
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
|
||||
nsSMILTimeValue timeVal(aCurrentTime + int64_t(NS_round(offset)));
|
||||
nsSMILTimeValue timeVal(aCurrentTime + int64_t(offset));
|
||||
|
||||
RefPtr<nsSMILInstanceTime> instanceTime =
|
||||
new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM);
|
||||
|
@ -164,26 +164,130 @@ FetchEvent::Constructor(const GlobalObject& aGlobal,
|
||||
|
||||
namespace {
|
||||
|
||||
struct RespondWithClosure
|
||||
{
|
||||
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
|
||||
const nsString mRequestURL;
|
||||
const nsCString mRespondWithScriptSpec;
|
||||
const uint32_t mRespondWithLineNumber;
|
||||
const uint32_t mRespondWithColumnNumber;
|
||||
|
||||
RespondWithClosure(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
|
||||
const nsAString& aRequestURL,
|
||||
const nsACString& aRespondWithScriptSpec,
|
||||
uint32_t aRespondWithLineNumber,
|
||||
uint32_t aRespondWithColumnNumber)
|
||||
: mInterceptedChannel(aChannel)
|
||||
, mRegistration(aRegistration)
|
||||
, mRequestURL(aRequestURL)
|
||||
, mRespondWithScriptSpec(aRespondWithScriptSpec)
|
||||
, mRespondWithLineNumber(aRespondWithLineNumber)
|
||||
, mRespondWithColumnNumber(aRespondWithColumnNumber)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class FinishResponse final : public Runnable
|
||||
{
|
||||
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||
|
||||
public:
|
||||
explicit FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
|
||||
: Runnable("dom::workers::FinishResponse")
|
||||
, mChannel(aChannel)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsresult rv = mChannel->FinishSynthesizedResponse();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
TimeStamp timeStamp = TimeStamp::Now();
|
||||
mChannel->SetHandleFetchEventEnd(timeStamp);
|
||||
mChannel->SetFinishSynthesizedResponseEnd(timeStamp);
|
||||
mChannel->SaveTimeStamps();
|
||||
|
||||
return rv;
|
||||
}
|
||||
};
|
||||
|
||||
class BodyCopyHandle final : public nsIInterceptedBodyCallback
|
||||
{
|
||||
UniquePtr<RespondWithClosure> mClosure;
|
||||
|
||||
~BodyCopyHandle()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
explicit BodyCopyHandle(UniquePtr<RespondWithClosure>&& aClosure)
|
||||
: mClosure(Move(aClosure))
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
BodyComplete(nsresult aRv) override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIRunnable> event;
|
||||
if (NS_WARN_IF(NS_FAILED(aRv))) {
|
||||
AsyncLog(mClosure->mInterceptedChannel, mClosure->mRespondWithScriptSpec,
|
||||
mClosure->mRespondWithLineNumber,
|
||||
mClosure->mRespondWithColumnNumber,
|
||||
NS_LITERAL_CSTRING("InterceptionFailedWithURL"),
|
||||
mClosure->mRequestURL);
|
||||
event = new CancelChannelRunnable(mClosure->mInterceptedChannel,
|
||||
mClosure->mRegistration,
|
||||
NS_ERROR_INTERCEPTION_FAILED);
|
||||
} else {
|
||||
event = new FinishResponse(mClosure->mInterceptedChannel);
|
||||
}
|
||||
|
||||
mClosure.reset();
|
||||
|
||||
event->Run();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(BodyCopyHandle, nsIInterceptedBodyCallback)
|
||||
|
||||
class StartResponse final : public Runnable
|
||||
{
|
||||
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
|
||||
RefPtr<InternalResponse> mInternalResponse;
|
||||
ChannelInfo mWorkerChannelInfo;
|
||||
const nsCString mScriptSpec;
|
||||
const nsCString mResponseURLSpec;
|
||||
UniquePtr<RespondWithClosure> mClosure;
|
||||
|
||||
public:
|
||||
FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||
InternalResponse* aInternalResponse,
|
||||
const ChannelInfo& aWorkerChannelInfo,
|
||||
const nsACString& aScriptSpec,
|
||||
const nsACString& aResponseURLSpec)
|
||||
: Runnable("dom::workers::FinishResponse")
|
||||
StartResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||
InternalResponse* aInternalResponse,
|
||||
const ChannelInfo& aWorkerChannelInfo,
|
||||
const nsACString& aScriptSpec,
|
||||
const nsACString& aResponseURLSpec,
|
||||
UniquePtr<RespondWithClosure>&& aClosure)
|
||||
: Runnable("dom::workers::StartResponse")
|
||||
, mChannel(aChannel)
|
||||
, mInternalResponse(aInternalResponse)
|
||||
, mWorkerChannelInfo(aWorkerChannelInfo)
|
||||
, mScriptSpec(aScriptSpec)
|
||||
, mResponseURLSpec(aResponseURLSpec)
|
||||
, mClosure(Move(aClosure))
|
||||
{
|
||||
}
|
||||
|
||||
@ -233,17 +337,18 @@ public:
|
||||
auto castLoadInfo = static_cast<LoadInfo*>(loadInfo.get());
|
||||
castLoadInfo->SynthesizeServiceWorkerTainting(mInternalResponse->GetTainting());
|
||||
|
||||
rv = mChannel->FinishSynthesizedResponse(mResponseURLSpec);
|
||||
nsCOMPtr<nsIInputStream> body;
|
||||
mInternalResponse->GetUnfilteredBody(getter_AddRefs(body));
|
||||
RefPtr<BodyCopyHandle> copyHandle;
|
||||
copyHandle = new BodyCopyHandle(Move(mClosure));
|
||||
|
||||
rv = mChannel->StartSynthesizedResponse(body, copyHandle,
|
||||
mResponseURLSpec);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
TimeStamp timeStamp = TimeStamp::Now();
|
||||
mChannel->SetHandleFetchEventEnd(timeStamp);
|
||||
mChannel->SetFinishSynthesizedResponseEnd(timeStamp);
|
||||
mChannel->SaveTimeStamps();
|
||||
|
||||
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
|
||||
if (obsService) {
|
||||
obsService->NotifyObservers(underlyingChannel, "service-worker-synthesized-response", nullptr);
|
||||
@ -251,6 +356,7 @@ public:
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool CSPPermitsResponse(nsILoadInfo* aLoadInfo)
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
@ -349,71 +455,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
struct RespondWithClosure
|
||||
{
|
||||
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
|
||||
RefPtr<InternalResponse> mInternalResponse;
|
||||
ChannelInfo mWorkerChannelInfo;
|
||||
const nsCString mScriptSpec;
|
||||
const nsCString mResponseURLSpec;
|
||||
const nsString mRequestURL;
|
||||
const nsCString mRespondWithScriptSpec;
|
||||
const uint32_t mRespondWithLineNumber;
|
||||
const uint32_t mRespondWithColumnNumber;
|
||||
|
||||
RespondWithClosure(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
|
||||
InternalResponse* aInternalResponse,
|
||||
const ChannelInfo& aWorkerChannelInfo,
|
||||
const nsCString& aScriptSpec,
|
||||
const nsACString& aResponseURLSpec,
|
||||
const nsAString& aRequestURL,
|
||||
const nsACString& aRespondWithScriptSpec,
|
||||
uint32_t aRespondWithLineNumber,
|
||||
uint32_t aRespondWithColumnNumber)
|
||||
: mInterceptedChannel(aChannel)
|
||||
, mRegistration(aRegistration)
|
||||
, mInternalResponse(aInternalResponse)
|
||||
, mWorkerChannelInfo(aWorkerChannelInfo)
|
||||
, mScriptSpec(aScriptSpec)
|
||||
, mResponseURLSpec(aResponseURLSpec)
|
||||
, mRequestURL(aRequestURL)
|
||||
, mRespondWithScriptSpec(aRespondWithScriptSpec)
|
||||
, mRespondWithLineNumber(aRespondWithLineNumber)
|
||||
, mRespondWithColumnNumber(aRespondWithColumnNumber)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
|
||||
{
|
||||
nsAutoPtr<RespondWithClosure> data(static_cast<RespondWithClosure*>(aClosure));
|
||||
nsCOMPtr<nsIRunnable> event;
|
||||
if (NS_WARN_IF(NS_FAILED(aStatus))) {
|
||||
AsyncLog(data->mInterceptedChannel, data->mRespondWithScriptSpec,
|
||||
data->mRespondWithLineNumber, data->mRespondWithColumnNumber,
|
||||
NS_LITERAL_CSTRING("InterceptionFailedWithURL"),
|
||||
data->mRequestURL);
|
||||
event = new CancelChannelRunnable(data->mInterceptedChannel,
|
||||
data->mRegistration,
|
||||
NS_ERROR_INTERCEPTION_FAILED);
|
||||
} else {
|
||||
event = new FinishResponse(data->mInterceptedChannel,
|
||||
data->mInternalResponse,
|
||||
data->mWorkerChannelInfo,
|
||||
data->mScriptSpec,
|
||||
data->mResponseURLSpec);
|
||||
}
|
||||
// In theory this can happen after the worker thread is terminated.
|
||||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||||
if (worker) {
|
||||
MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(event.forget()));
|
||||
} else {
|
||||
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event.forget()));
|
||||
}
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS AutoCancel
|
||||
{
|
||||
RefPtr<RespondWithHandler> mOwner;
|
||||
@ -627,15 +668,21 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
|
||||
return;
|
||||
}
|
||||
}
|
||||
nsAutoPtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel,
|
||||
mRegistration, ir,
|
||||
worker->GetChannelInfo(),
|
||||
mScriptSpec,
|
||||
responseURL,
|
||||
|
||||
UniquePtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel,
|
||||
mRegistration,
|
||||
mRequestURL,
|
||||
mRespondWithScriptSpec,
|
||||
mRespondWithLineNumber,
|
||||
mRespondWithColumnNumber));
|
||||
|
||||
nsCOMPtr<nsIRunnable> startRunnable = new StartResponse(mInterceptedChannel,
|
||||
ir,
|
||||
worker->GetChannelInfo(),
|
||||
mScriptSpec,
|
||||
responseURL,
|
||||
Move(closure));
|
||||
|
||||
nsCOMPtr<nsIInputStream> body;
|
||||
ir->GetUnfilteredBody(getter_AddRefs(body));
|
||||
// Errors and redirects may not have a body.
|
||||
@ -646,48 +693,10 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
|
||||
autoCancel.SetCancelErrorResult(aCx, error);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIOutputStream> responseBody;
|
||||
rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
|
||||
if (NS_WARN_IF(NS_FAILED(rv)) || !responseBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t kCopySegmentSize = 4096;
|
||||
|
||||
// Depending on how the Response passed to .respondWith() was created, we may
|
||||
// get a non-buffered input stream. In addition, in some configurations the
|
||||
// destination channel's output stream can be unbuffered. We wrap the output
|
||||
// stream side here so that NS_AsyncCopy() works. Wrapping the output side
|
||||
// provides the most consistent operation since there are fewer stream types
|
||||
// we are writing to. The input stream can be a wide variety of concrete
|
||||
// objects which may or many not play well with NS_InputStreamIsBuffered().
|
||||
if (!NS_OutputStreamIsBuffered(responseBody)) {
|
||||
nsCOMPtr<nsIOutputStream> buffered;
|
||||
rv = NS_NewBufferedOutputStream(getter_AddRefs(buffered), responseBody,
|
||||
kCopySegmentSize);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
responseBody = buffered;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIEventTarget> stsThread = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
|
||||
if (NS_WARN_IF(!stsThread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXXnsm, Fix for Bug 1141332 means that if we decide to make this
|
||||
// streaming at some point, we'll need a different solution to that bug.
|
||||
rv = NS_AsyncCopy(body, responseBody, stsThread, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
|
||||
kCopySegmentSize, RespondWithCopyComplete, closure.forget());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
RespondWithCopyComplete(closure.forget(), NS_OK);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(startRunnable.forget()));
|
||||
|
||||
MOZ_ASSERT(!closure);
|
||||
autoCancel.Reset();
|
||||
mRequestWasHandled = true;
|
||||
|
@ -1,42 +0,0 @@
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.importGlobalProperties(["File"]);
|
||||
|
||||
var fileNum = 1;
|
||||
|
||||
function createFileWithData(fileData) {
|
||||
var willDelete = fileData === null;
|
||||
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
|
||||
var testFile = dirSvc.get("ProfD", Ci.nsIFile);
|
||||
testFile.append("fileAPItestfile" + fileNum);
|
||||
fileNum++;
|
||||
var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
|
||||
outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
|
||||
0o666, 0);
|
||||
if (willDelete) {
|
||||
fileData = "some irrelevant test data\n";
|
||||
}
|
||||
outStream.write(fileData, fileData.length);
|
||||
outStream.close();
|
||||
|
||||
return File.createFromNsIFile(testFile).then(function(domFile) {
|
||||
if (willDelete) {
|
||||
testFile.remove(/* recursive: */ false);
|
||||
}
|
||||
return domFile;
|
||||
});
|
||||
}
|
||||
|
||||
addMessageListener("files.open", function (message) {
|
||||
let promises = [];
|
||||
let list = [];
|
||||
|
||||
for (let fileData of message) {
|
||||
promises.push(createFileWithData(fileData).then(domFile => {
|
||||
list.push(domFile);
|
||||
}));
|
||||
}
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
sendAsyncMessage("files.opened", list);
|
||||
});
|
||||
});
|
@ -98,8 +98,6 @@ support-files =
|
||||
worker_referrer.js
|
||||
websocket_https.html
|
||||
websocket_https_worker.js
|
||||
worker_fileReader.js
|
||||
fileapi_chromeScript.js
|
||||
importScripts_3rdParty_worker.js
|
||||
worker_bug1278777.js
|
||||
worker_setTimeoutWith0.js
|
||||
@ -229,8 +227,6 @@ scheme=https
|
||||
[test_importScripts_3rdparty.html]
|
||||
[test_sharedWorker_ports.html]
|
||||
[test_sharedWorker_lifetime.html]
|
||||
[test_fileReader.html]
|
||||
skip-if = !debug # bug 1400098
|
||||
[test_navigator_workers_hardwareConcurrency.html]
|
||||
[test_bug1278777.html]
|
||||
[test_setTimeoutWith0.html]
|
||||
|
@ -9,6 +9,7 @@ support-files =
|
||||
serviceworkerinfo_iframe.html
|
||||
serviceworkermanager_iframe.html
|
||||
serviceworkerregistrationinfo_iframe.html
|
||||
utils.js
|
||||
worker.js
|
||||
worker2.js
|
||||
|
||||
|
@ -6,6 +6,8 @@ addEventListener('fetch', function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
addEventListener("activate", function(event) {
|
||||
event.waitUntil(clients.claim());
|
||||
addEventListener('message', function(event) {
|
||||
if (event.data === 'claim') {
|
||||
event.waitUntil(clients.claim());
|
||||
}
|
||||
});
|
||||
|
@ -16,6 +16,7 @@
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script src="utils.js"></script>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
// Constants
|
||||
@ -57,12 +58,25 @@ function executeTest(aWindow) {
|
||||
.then(_ => register(aWindow, workerURL, workerScope))
|
||||
.then(r => registration = r)
|
||||
|
||||
// Should be intercpeted and synthesized.
|
||||
// If this test is re-run then we may end up resurrecting the previous
|
||||
// registration and worker. In those cases we will have an active instead
|
||||
// of installing. This happens because because the test window itself
|
||||
// is controlled. If we were using iframes we could ensure the registration
|
||||
// was removed before ending the test.
|
||||
.then(_ => waitForState(registration.installing || registration.active, 'activated'))
|
||||
|
||||
// When run consecutively we sometime end up resurrecting a previous
|
||||
// service worker. In that case our active event does not run and claim
|
||||
// the window. So do the claim for a message event instead.
|
||||
.then(_ => registration.active.postMessage('claim'))
|
||||
.then(_ => waitForControlled(aWindow))
|
||||
|
||||
// Should be intercepted and synthesized.
|
||||
.then(_ => fetchAndCheckTimedChannel(aWindow, true, false, "fake.html"))
|
||||
|
||||
// Should be intercepted but still fetch from network.
|
||||
.then(_ => fetchAndCheckTimedChannel(aWindow, true, true,
|
||||
"hello.html?ForBypassingHttpCache"))
|
||||
"hello.html?ForBypassingHttpCache=" + Date.now()))
|
||||
|
||||
// Tear down
|
||||
.then(_ => registration.unregister())
|
||||
@ -87,8 +101,7 @@ function fetchAndCheckTimedChannel(aWindow, aIntercepted, aFetch, aURL) {
|
||||
var resolveFunction;
|
||||
var promise = new Promise(aResolve => resolveFunction = aResolve);
|
||||
|
||||
var topic = aFetch ? "http-on-examine-response"
|
||||
: "service-worker-synthesized-response";
|
||||
var topic = "http-on-stop-request";
|
||||
|
||||
function observer(aSubject) {
|
||||
var channel = aSubject.QueryInterface(Ci.nsIChannel);
|
||||
|
@ -56,6 +56,8 @@ add_task(async function test_integrity_serviceWorker() {
|
||||
|
||||
let registration = await navigator.serviceWorker.register("fetch.js",
|
||||
{ scope: "./" });
|
||||
let worker = registration.installing || registration.active;
|
||||
worker.postMessage('claim');
|
||||
await waitForControlled;
|
||||
|
||||
info("Test for mNavigationInterceptions.")
|
||||
|
@ -12,3 +12,14 @@ function waitForState(worker, state, context) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function waitForControlled(win) {
|
||||
return new Promise(resolve => {
|
||||
if (win.navigator.serviceWorker.controller) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
win.navigator.serviceWorker.addEventListener('controllerchange', resolve,
|
||||
{ once: true });
|
||||
});
|
||||
}
|
||||
|
@ -1,100 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for FileReader in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
const minFileSize = 20000;
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Create strings containing data we'll test with. We'll want long
|
||||
// strings to ensure they span multiple buffers while loading
|
||||
var testTextData = "asd b\tlah\u1234w\u00a0r";
|
||||
while (testTextData.length < minFileSize) {
|
||||
testTextData = testTextData + testTextData;
|
||||
}
|
||||
|
||||
var testASCIIData = "abcdef 123456\n";
|
||||
while (testASCIIData.length < minFileSize) {
|
||||
testASCIIData = testASCIIData + testASCIIData;
|
||||
}
|
||||
|
||||
var testBinaryData = "";
|
||||
for (var i = 0; i < 256; i++) {
|
||||
testBinaryData += String.fromCharCode(i);
|
||||
}
|
||||
while (testBinaryData.length < minFileSize) {
|
||||
testBinaryData = testBinaryData + testBinaryData;
|
||||
}
|
||||
|
||||
var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
|
||||
testBinaryData.length % 3);
|
||||
var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
|
||||
testBinaryData.length % 3);
|
||||
var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
|
||||
testBinaryData.length % 3);
|
||||
|
||||
|
||||
//Set up files for testing
|
||||
var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
|
||||
var opener = SpecialPowers.loadChromeScript(openerURL);
|
||||
opener.addMessageListener("files.opened", onFilesOpened);
|
||||
opener.sendAsyncMessage("files.open", [
|
||||
testASCIIData,
|
||||
testBinaryData,
|
||||
null,
|
||||
convertToUTF8(testTextData),
|
||||
convertToUTF16(testTextData),
|
||||
"",
|
||||
dataurldata0,
|
||||
dataurldata1,
|
||||
dataurldata2,
|
||||
]);
|
||||
|
||||
function onFilesOpened(message) {
|
||||
var worker = new Worker('worker_fileReader.js');
|
||||
worker.postMessage({ blobs: message,
|
||||
testTextData: testTextData,
|
||||
testASCIIData: testASCIIData,
|
||||
testBinaryData: testBinaryData,
|
||||
dataurldata0: dataurldata0,
|
||||
dataurldata1: dataurldata1,
|
||||
dataurldata2: dataurldata2 });
|
||||
|
||||
worker.onmessage = function(e) {
|
||||
var msg = e.data;
|
||||
if (msg.type == 'finish') {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type == 'check') {
|
||||
ok(msg.status, msg.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "Unknown message.");
|
||||
}
|
||||
}
|
||||
|
||||
function convertToUTF16(s) {
|
||||
res = "";
|
||||
for (var i = 0; i < s.length; ++i) {
|
||||
c = s.charCodeAt(i);
|
||||
res += String.fromCharCode(c & 255, c >>> 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function convertToUTF8(s) {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,419 +0,0 @@
|
||||
var testRanCounter = 0;
|
||||
var expectedTestCount = 0;
|
||||
var testSetupFinished = false;
|
||||
|
||||
function ok(a, msg) {
|
||||
postMessage({type: 'check', status: !!a, msg: msg });
|
||||
}
|
||||
|
||||
function is(a, b, msg) {
|
||||
ok(a === b, msg);
|
||||
}
|
||||
|
||||
function finish() {
|
||||
postMessage({type: 'finish'});
|
||||
}
|
||||
|
||||
function convertToUTF16(s) {
|
||||
res = "";
|
||||
for (var i = 0; i < s.length; ++i) {
|
||||
c = s.charCodeAt(i);
|
||||
res += String.fromCharCode(c & 255, c >>> 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function convertToUTF8(s) {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
function convertToDataURL(s) {
|
||||
return "data:application/octet-stream;base64," + btoa(s);
|
||||
}
|
||||
|
||||
onmessage = function(message) {
|
||||
is(FileReader.EMPTY, 0, "correct EMPTY value");
|
||||
is(FileReader.LOADING, 1, "correct LOADING value");
|
||||
is(FileReader.DONE, 2, "correct DONE value");
|
||||
|
||||
// List of blobs.
|
||||
var asciiFile = message.data.blobs.shift();
|
||||
var binaryFile = message.data.blobs.shift();
|
||||
var nonExistingFile = message.data.blobs.shift();
|
||||
var utf8TextFile = message.data.blobs.shift();
|
||||
var utf16TextFile = message.data.blobs.shift();
|
||||
var emptyFile = message.data.blobs.shift();
|
||||
var dataUrlFile0 = message.data.blobs.shift();
|
||||
var dataUrlFile1 = message.data.blobs.shift();
|
||||
var dataUrlFile2 = message.data.blobs.shift();
|
||||
|
||||
// List of buffers for testing.
|
||||
var testTextData = message.data.testTextData;
|
||||
var testASCIIData = message.data.testASCIIData;
|
||||
var testBinaryData = message.data.testBinaryData;
|
||||
var dataurldata0 = message.data.dataurldata0;
|
||||
var dataurldata1 = message.data.dataurldata1;
|
||||
var dataurldata2 = message.data.dataurldata2;
|
||||
|
||||
// Test that plain reading works and fires events as expected, both
|
||||
// for text and binary reading
|
||||
|
||||
var onloadHasRunText = false;
|
||||
var onloadStartHasRunText = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
|
||||
r.addEventListener("load", function() { onloadHasRunText = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunText = true });
|
||||
r.readAsText(asciiFile);
|
||||
is(r.readyState, FileReader.LOADING, "correct loading text readyState");
|
||||
is(onloadHasRunText, false, "text loading must be async");
|
||||
is(onloadStartHasRunText, true, "text loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
var onloadHasRunBinary = false;
|
||||
var onloadStartHasRunBinary = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
|
||||
r.addEventListener("load", function() { onloadHasRunBinary = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true });
|
||||
r.readAsBinaryString(binaryFile);
|
||||
r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
|
||||
is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
|
||||
is(onloadHasRunBinary, false, "binary loading must be async");
|
||||
is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
var onloadHasRunArrayBuffer = false;
|
||||
var onloadStartHasRunArrayBuffer = false;
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
|
||||
r.addEventListener("load", function() { onloadHasRunArrayBuffer = true });
|
||||
r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true });
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
|
||||
is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
|
||||
is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
|
||||
is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
|
||||
expectedTestCount++;
|
||||
|
||||
// Test a variety of encodings, and make sure they work properly
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
|
||||
r.readAsText(asciiFile, "");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
|
||||
r.readAsText(asciiFile, "iso-8859-1");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testTextData,
|
||||
convertToUTF8(testTextData).length,
|
||||
"utf8 reading");
|
||||
r.readAsText(utf8TextFile, "utf8");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.readAsText(utf16TextFile, "utf-16");
|
||||
r.onload = getLoadHandler(testTextData,
|
||||
convertToUTF16(testTextData).length,
|
||||
"utf16 reading");
|
||||
expectedTestCount++;
|
||||
|
||||
// Test get result without reading
|
||||
r = new FileReader();
|
||||
is(r.readyState, FileReader.EMPTY,
|
||||
"readyState in test reader get result without reading");
|
||||
is(r.error, null,
|
||||
"no error in test reader get result without reading");
|
||||
is(r.result, null,
|
||||
"result in test reader get result without reading");
|
||||
|
||||
// Test loading an empty file works (and doesn't crash!)
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty no encoding reading");
|
||||
r.readAsText(emptyFile, "");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty utf8 reading");
|
||||
r.readAsText(emptyFile, "utf8");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty utf16 reading");
|
||||
r.readAsText(emptyFile, "utf-16");
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler("", 0, "empty binary string reading");
|
||||
r.readAsBinaryString(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
|
||||
r.readAsArrayBuffer(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(""), 0, "empty binary string reading");
|
||||
r.readAsDataURL(emptyFile);
|
||||
expectedTestCount++;
|
||||
|
||||
// Test reusing a FileReader to read multiple times
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testASCIIData,
|
||||
testASCIIData.length,
|
||||
"to-be-reused reading text")
|
||||
var makeAnotherReadListener = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener);
|
||||
r.onload = getLoadHandler(testASCIIData,
|
||||
testASCIIData.length,
|
||||
"reused reading text");
|
||||
r.readAsText(asciiFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener);
|
||||
r.readAsText(asciiFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading binary")
|
||||
var makeAnotherReadListener2 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener2);
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading binary");
|
||||
r.readAsBinaryString(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener2);
|
||||
r.readAsBinaryString(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(testBinaryData),
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading data url")
|
||||
var makeAnotherReadListener3 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener3);
|
||||
r.onload = getLoadHandler(convertToDataURL(testBinaryData),
|
||||
testBinaryData.length,
|
||||
"reused reading data url");
|
||||
r.readAsDataURL(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener3);
|
||||
r.readAsDataURL(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading arrayBuffer")
|
||||
var makeAnotherReadListener4 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener4);
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading arrayBuffer");
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener4);
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
// Test first reading as ArrayBuffer then read as something else
|
||||
// (BinaryString) and doesn't crash
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"to-be-reused reading arrayBuffer")
|
||||
var makeAnotherReadListener5 = function(event) {
|
||||
r = event.target;
|
||||
r.removeEventListener("load", makeAnotherReadListener5);
|
||||
r.onload = getLoadHandler(testBinaryData,
|
||||
testBinaryData.length,
|
||||
"reused reading binary string");
|
||||
r.readAsBinaryString(binaryFile);
|
||||
};
|
||||
r.addEventListener("load", makeAnotherReadListener5);
|
||||
r.readAsArrayBuffer(binaryFile);
|
||||
expectedTestCount += 2;
|
||||
|
||||
//Test data-URI encoding on differing file sizes
|
||||
is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata0),
|
||||
dataurldata0.length,
|
||||
"dataurl reading, %3 = 0");
|
||||
r.readAsDataURL(dataUrlFile0);
|
||||
expectedTestCount++;
|
||||
|
||||
is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata1),
|
||||
dataurldata1.length,
|
||||
"dataurl reading, %3 = 1");
|
||||
r.readAsDataURL(dataUrlFile1);
|
||||
expectedTestCount++;
|
||||
|
||||
is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
|
||||
r = new FileReader();
|
||||
r.onload = getLoadHandler(convertToDataURL(dataurldata2),
|
||||
dataurldata2.length,
|
||||
"dataurl reading, %3 = 2");
|
||||
r.readAsDataURL(dataUrlFile2),
|
||||
expectedTestCount++;
|
||||
|
||||
|
||||
// Test abort()
|
||||
var abortHasRun = false;
|
||||
var loadEndHasRun = false;
|
||||
r = new FileReader();
|
||||
r.onabort = function (event) {
|
||||
is(abortHasRun, false, "abort should only fire once");
|
||||
is(loadEndHasRun, false, "loadend shouldn't have fired yet");
|
||||
abortHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onloadend = function (event) {
|
||||
is(abortHasRun, true, "abort should fire before loadend");
|
||||
is(loadEndHasRun, false, "loadend should only fire once");
|
||||
loadEndHasRun = true;
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
|
||||
is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
}
|
||||
r.onload = function() { ok(false, "load should not fire for aborted reads") };
|
||||
r.onerror = function() { ok(false, "error should not fire for aborted reads") };
|
||||
r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
|
||||
var abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
is(abortThrew, false, "abort() never throws");
|
||||
is(abortHasRun, false, "abort() is a no-op unless loading");
|
||||
r.readAsText(asciiFile);
|
||||
r.abort();
|
||||
is(abortHasRun, true, "1 abort should fire sync");
|
||||
is(loadEndHasRun, true, "loadend should fire sync");
|
||||
|
||||
// Test calling readAsX to cause abort()
|
||||
var reuseAbortHasRun = false;
|
||||
r = new FileReader();
|
||||
r.onabort = function (event) { reuseAbortHasRun = true; }
|
||||
r.onload = function() { ok(true, "load should fire for aborted reads") };
|
||||
var abortThrew = false;
|
||||
try {
|
||||
r.abort();
|
||||
} catch(e) {
|
||||
abortThrew = true;
|
||||
}
|
||||
is(abortThrew, false, "abort() never throws");
|
||||
is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
|
||||
r.readAsText(asciiFile);
|
||||
|
||||
var readThrew = false;
|
||||
try {
|
||||
r.readAsText(asciiFile);
|
||||
} catch(e) {
|
||||
readThrew = true;
|
||||
}
|
||||
|
||||
is(readThrew, true, "readAsText() must throw if loading");
|
||||
is(reuseAbortHasRun, false, "2 abort should fire sync");
|
||||
r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
|
||||
expectedTestCount++;
|
||||
|
||||
|
||||
// Test reading from nonexistent files
|
||||
r = new FileReader();
|
||||
var didThrow = false;
|
||||
r.onerror = function (event) {
|
||||
is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
|
||||
is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
|
||||
is(event.target.result, null, "file data should be null on aborted reads");
|
||||
testHasRun();
|
||||
};
|
||||
r.onload = function (event) {
|
||||
is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
|
||||
testHasRun();
|
||||
};
|
||||
try {
|
||||
r.readAsDataURL(nonExistingFile);
|
||||
expectedTestCount++;
|
||||
} catch(ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
// Once this test passes, we should test that onerror gets called and
|
||||
// that the FileReader object is in the right state during that call.
|
||||
is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
|
||||
|
||||
|
||||
function getLoadHandler(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"readyState in test " + testName);
|
||||
is(event.target.error, null,
|
||||
"no error in test " + testName);
|
||||
is(event.target.result, expectedResult,
|
||||
"result in test " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"loaded in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"total in test " + testName);
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
|
||||
return function (event) {
|
||||
is(event.target.readyState, FileReader.DONE,
|
||||
"readyState in test " + testName);
|
||||
is(event.target.error, null,
|
||||
"no error in test " + testName);
|
||||
is(event.lengthComputable, true,
|
||||
"lengthComputable in test " + testName);
|
||||
is(event.loaded, expectedLength,
|
||||
"loaded in test " + testName);
|
||||
is(event.total, expectedLength,
|
||||
"total in test " + testName);
|
||||
is(event.target.result.byteLength, expectedLength,
|
||||
"array buffer size in test " + testName);
|
||||
var u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), expectedResult,
|
||||
"array buffer contents in test " + testName);
|
||||
u8v = null;
|
||||
is(event.target.result.byteLength, expectedLength,
|
||||
"array buffer size after gc in test " + testName);
|
||||
u8v = new Uint8Array(event.target.result);
|
||||
is(String.fromCharCode.apply(String, u8v), expectedResult,
|
||||
"array buffer contents after gc in test " + testName);
|
||||
testHasRun();
|
||||
}
|
||||
}
|
||||
|
||||
function testHasRun() {
|
||||
//alert(testRanCounter);
|
||||
++testRanCounter;
|
||||
if (testRanCounter == expectedTestCount) {
|
||||
is(testSetupFinished, true, "test setup should have finished; check for exceptions");
|
||||
is(onloadHasRunText, true, "onload text should have fired by now");
|
||||
is(onloadHasRunBinary, true, "onload binary should have fired by now");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
testSetupFinished = true;
|
||||
}
|
@ -2716,8 +2716,8 @@ HTMLEditor::GetCellDataAt(nsIDOMElement* aTable,
|
||||
}
|
||||
|
||||
*aIsSelected = cellFrame->IsSelected();
|
||||
cellFrame->GetRowIndex(*aStartRowIndex);
|
||||
cellFrame->GetColIndex(*aStartColIndex);
|
||||
*aStartRowIndex = cellFrame->RowIndex();
|
||||
*aStartColIndex = cellFrame->ColIndex();
|
||||
*aRowSpan = cellFrame->GetRowSpan();
|
||||
*aColSpan = cellFrame->GetColSpan();
|
||||
*aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
|
||||
|
@ -1962,6 +1962,27 @@ MLGDeviceD3D11::CopyTexture(MLGTexture* aDest,
|
||||
MLGTextureD3D11* dest = aDest->AsD3D11();
|
||||
MLGTextureD3D11* source = aSource->AsD3D11();
|
||||
|
||||
// We check both the source and destination copy regions, because
|
||||
// CopySubresourceRegion is documented as causing a device reset if
|
||||
// the operation is out-of-bounds. And it's not lying.
|
||||
IntRect sourceBounds(IntPoint(0, 0), aSource->GetSize());
|
||||
if (!sourceBounds.Contains(aRect)) {
|
||||
gfxWarning() << "Attempt to read out-of-bounds in CopySubresourceRegion: " <<
|
||||
Stringify(sourceBounds) <<
|
||||
", " <<
|
||||
Stringify(aRect);
|
||||
return;
|
||||
}
|
||||
|
||||
IntRect destBounds(IntPoint(0, 0), aDest->GetSize());
|
||||
if (!destBounds.Contains(IntRect(aTarget, aRect.Size()))) {
|
||||
gfxWarning() << "Attempt to write out-of-bounds in CopySubresourceRegion: " <<
|
||||
Stringify(destBounds) <<
|
||||
", " <<
|
||||
Stringify(aTarget) << ", " << Stringify(aRect.Size());
|
||||
return;
|
||||
}
|
||||
|
||||
D3D11_BOX box = RectToBox(aRect);
|
||||
mCtx->CopySubresourceRegion(
|
||||
dest->GetTexture(), 0,
|
||||
|
@ -1020,25 +1020,28 @@ RenderViewPass::RenderWithBackdropCopy()
|
||||
MOZ_ASSERT(transform.Is2D(&transform2d) &&
|
||||
!gfx::ThebesMatrix(transform2d).HasNonIntegerTranslation());
|
||||
|
||||
IntPoint translation = IntPoint::Truncate(transform._41, transform._42);
|
||||
|
||||
RenderViewMLGPU* childView = mAssignedLayer->GetRenderView();
|
||||
|
||||
IntRect visible = mAssignedLayer->GetShadowVisibleRegion().GetBounds().ToUnknownRect();
|
||||
visible += IntPoint::Truncate(transform._41, transform._42);
|
||||
visible -= mParentView->GetTargetOffset();
|
||||
IntRect sourceRect = visible + translation - mParentView->GetTargetOffset();
|
||||
IntPoint destPoint = visible.TopLeft() - childView->GetTargetOffset();
|
||||
|
||||
RefPtr<MLGTexture> dest = mAssignedLayer->GetRenderTarget()->GetTexture();
|
||||
RefPtr<MLGTexture> source = mParentView->GetRenderTarget()->GetTexture();
|
||||
|
||||
// Clamp the rect so that we don't read pixels outside the source texture, or
|
||||
// write pixels outside the destination texture.
|
||||
visible = visible.Intersect(IntRect(IntPoint(0, 0), source->GetSize()));
|
||||
visible = visible.Intersect(IntRect(visible.TopLeft(), dest->GetSize()));
|
||||
// Clamp the source rect to the source texture size.
|
||||
sourceRect = sourceRect.Intersect(IntRect(IntPoint(0, 0), source->GetSize()));
|
||||
|
||||
mDevice->CopyTexture(dest, IntPoint(0, 0), source, visible);
|
||||
// Clamp the source rect to the destination texture size.
|
||||
IntRect destRect(destPoint, sourceRect.Size());
|
||||
destRect = destRect.Intersect(IntRect(IntPoint(0, 0), dest->GetSize()));
|
||||
sourceRect = sourceRect.Intersect(IntRect(sourceRect.TopLeft(), destRect.Size()));
|
||||
|
||||
RenderViewMLGPU* childView = mAssignedLayer->GetRenderView();
|
||||
mDevice->CopyTexture(dest, destPoint, source, sourceRect);
|
||||
childView->RenderAfterBackdropCopy();
|
||||
|
||||
mParentView->RestoreDeviceState();
|
||||
|
||||
TexturedRenderPass::ExecuteRendering();
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,13 @@ void
|
||||
RenderViewMLGPU::RenderAfterBackdropCopy()
|
||||
{
|
||||
MOZ_ASSERT(mContainer && mContainer->NeedsSurfaceCopy());
|
||||
|
||||
// Update the invalid bounds based on the container's visible region. This
|
||||
// of course won't affect the prepared pipeline, but it will change the
|
||||
// scissor rect in SetDeviceState.
|
||||
mInvalidBounds = mContainer->GetShadowVisibleRegion().GetBounds().ToUnknownRect() -
|
||||
GetTargetOffset();
|
||||
|
||||
ExecuteRendering();
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,8 @@ WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* a
|
||||
bool apzEnabled = mManager->AsyncPanZoomEnabled();
|
||||
EventRegions eventRegions;
|
||||
|
||||
for (nsDisplayItem* i = aDisplayList->GetBottom(); i; i = i->GetAbove()) {
|
||||
FlattenedDisplayItemIterator iter(aDisplayListBuilder, aDisplayList);
|
||||
while (nsDisplayItem* i = iter.GetNext()) {
|
||||
nsDisplayItem* item = i;
|
||||
DisplayItemType itemType = item->GetType();
|
||||
|
||||
@ -132,7 +133,7 @@ WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* a
|
||||
// if necessary.
|
||||
AutoTArray<nsDisplayItem*, 1> mergedItems;
|
||||
mergedItems.AppendElement(item);
|
||||
for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
|
||||
while (nsDisplayItem* peek = iter.PeekNext()) {
|
||||
if (!item->CanMerge(peek)) {
|
||||
break;
|
||||
}
|
||||
@ -140,7 +141,7 @@ WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* a
|
||||
mergedItems.AppendElement(peek);
|
||||
|
||||
// Move the iterator forward since we will merge this item.
|
||||
i = peek;
|
||||
i = iter.GetNext();
|
||||
}
|
||||
|
||||
if (mergedItems.Length() > 1) {
|
||||
@ -212,21 +213,14 @@ WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* a
|
||||
}
|
||||
}
|
||||
|
||||
nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
|
||||
if (item->ShouldFlattenAway(aDisplayListBuilder)) {
|
||||
MOZ_ASSERT(childItems);
|
||||
CreateWebRenderCommandsFromDisplayList(childItems, aDisplayListBuilder, aSc,
|
||||
aBuilder, aResources);
|
||||
} else {
|
||||
// ensure the scope of ScrollingLayersHelper is maintained
|
||||
ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, apzEnabled);
|
||||
// ensure the scope of ScrollingLayersHelper is maintained
|
||||
ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, apzEnabled);
|
||||
|
||||
// Note: this call to CreateWebRenderCommands can recurse back into
|
||||
// this function if the |item| is a wrapper for a sublist.
|
||||
if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager,
|
||||
aDisplayListBuilder)) {
|
||||
PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
|
||||
}
|
||||
// Note: this call to CreateWebRenderCommands can recurse back into
|
||||
// this function if the |item| is a wrapper for a sublist.
|
||||
if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager,
|
||||
aDisplayListBuilder)) {
|
||||
PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
|
||||
}
|
||||
|
||||
if (apzEnabled) {
|
||||
|
@ -817,9 +817,8 @@ IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initialize
|
||||
args[1].set(locales);
|
||||
args[2].set(options);
|
||||
|
||||
RootedValue thisv(cx, NullValue());
|
||||
RootedValue ignored(cx);
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, NullHandleValue, args, &ignored))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(ignored.isUndefined(),
|
||||
@ -846,8 +845,7 @@ LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> init
|
||||
args[3].set(options);
|
||||
args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
|
||||
|
||||
RootedValue thisv(cx, NullValue());
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, NullHandleValue, args, result))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
|
||||
@ -899,8 +897,8 @@ GetInternals(JSContext* cx, HandleObject obj)
|
||||
|
||||
args[0].setObject(*obj);
|
||||
|
||||
RootedValue v(cx, NullValue());
|
||||
if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, v, args, &v))
|
||||
RootedValue v(cx);
|
||||
if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, NullHandleValue, args, &v))
|
||||
return nullptr;
|
||||
|
||||
return &v.toObject();
|
||||
@ -1056,8 +1054,8 @@ Collator(JSContext* cx, const CallArgs& args)
|
||||
collator->setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
|
||||
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
|
||||
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 6.
|
||||
if (!IntlInitialize(cx, collator, cx->names().InitializeCollator, locales, options))
|
||||
@ -1235,39 +1233,42 @@ NewUCollator(JSContext* cx, Handle<CollatorObject*> collator)
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().usage, &value))
|
||||
return nullptr;
|
||||
JSLinearString* usage = value.toString()->ensureLinear(cx);
|
||||
if (!usage)
|
||||
return nullptr;
|
||||
if (StringEqualsAscii(usage, "search")) {
|
||||
// ICU expects search as a Unicode locale extension on locale.
|
||||
// Unicode locale extensions must occur before private use extensions.
|
||||
const char* oldLocale = locale.ptr();
|
||||
const char* p;
|
||||
size_t index;
|
||||
size_t localeLen = strlen(oldLocale);
|
||||
if ((p = strstr(oldLocale, "-x-")))
|
||||
index = p - oldLocale;
|
||||
else
|
||||
index = localeLen;
|
||||
|
||||
const char* insert;
|
||||
if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) {
|
||||
index = p - oldLocale + 2;
|
||||
insert = "-co-search";
|
||||
} else {
|
||||
insert = "-u-co-search";
|
||||
}
|
||||
size_t insertLen = strlen(insert);
|
||||
char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1);
|
||||
if (!newLocale)
|
||||
{
|
||||
JSLinearString* usage = value.toString()->ensureLinear(cx);
|
||||
if (!usage)
|
||||
return nullptr;
|
||||
memcpy(newLocale, oldLocale, index);
|
||||
memcpy(newLocale + index, insert, insertLen);
|
||||
memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
|
||||
locale.clear();
|
||||
locale.initBytes(JS::UniqueChars(newLocale));
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(usage, "sort"));
|
||||
if (StringEqualsAscii(usage, "search")) {
|
||||
// ICU expects search as a Unicode locale extension on locale.
|
||||
// Unicode locale extensions must occur before private use extensions.
|
||||
const char* oldLocale = locale.ptr();
|
||||
const char* p;
|
||||
size_t index;
|
||||
size_t localeLen = strlen(oldLocale);
|
||||
if ((p = strstr(oldLocale, "-x-")))
|
||||
index = p - oldLocale;
|
||||
else
|
||||
index = localeLen;
|
||||
|
||||
const char* insert;
|
||||
if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) {
|
||||
index = p - oldLocale + 2;
|
||||
insert = "-co-search";
|
||||
} else {
|
||||
insert = "-u-co-search";
|
||||
}
|
||||
size_t insertLen = strlen(insert);
|
||||
char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1);
|
||||
if (!newLocale)
|
||||
return nullptr;
|
||||
memcpy(newLocale, oldLocale, index);
|
||||
memcpy(newLocale + index, insert, insertLen);
|
||||
memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
|
||||
locale.clear();
|
||||
locale.initBytes(JS::UniqueChars(newLocale));
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(usage, "sort"));
|
||||
}
|
||||
}
|
||||
|
||||
// We don't need to look at the collation property - it can only be set
|
||||
@ -1276,19 +1277,22 @@ NewUCollator(JSContext* cx, Handle<CollatorObject*> collator)
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value))
|
||||
return nullptr;
|
||||
JSLinearString* sensitivity = value.toString()->ensureLinear(cx);
|
||||
if (!sensitivity)
|
||||
return nullptr;
|
||||
if (StringEqualsAscii(sensitivity, "base")) {
|
||||
uStrength = UCOL_PRIMARY;
|
||||
} else if (StringEqualsAscii(sensitivity, "accent")) {
|
||||
uStrength = UCOL_SECONDARY;
|
||||
} else if (StringEqualsAscii(sensitivity, "case")) {
|
||||
uStrength = UCOL_PRIMARY;
|
||||
uCaseLevel = UCOL_ON;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(sensitivity, "variant"));
|
||||
uStrength = UCOL_TERTIARY;
|
||||
|
||||
{
|
||||
JSLinearString* sensitivity = value.toString()->ensureLinear(cx);
|
||||
if (!sensitivity)
|
||||
return nullptr;
|
||||
if (StringEqualsAscii(sensitivity, "base")) {
|
||||
uStrength = UCOL_PRIMARY;
|
||||
} else if (StringEqualsAscii(sensitivity, "accent")) {
|
||||
uStrength = UCOL_SECONDARY;
|
||||
} else if (StringEqualsAscii(sensitivity, "case")) {
|
||||
uStrength = UCOL_PRIMARY;
|
||||
uCaseLevel = UCOL_ON;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(sensitivity, "variant"));
|
||||
uStrength = UCOL_TERTIARY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
|
||||
@ -1625,8 +1629,8 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
|
||||
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
|
||||
|
||||
RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
|
||||
@ -1734,7 +1738,7 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
|
||||
ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
|
||||
|
||||
const char* name = unumsys_getName(numbers);
|
||||
RootedString jsname(cx, JS_NewStringCopyZ(cx, name));
|
||||
JSString* jsname = JS_NewStringCopyZ(cx, name);
|
||||
if (!jsname)
|
||||
return false;
|
||||
|
||||
@ -1858,39 +1862,42 @@ NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value))
|
||||
return nullptr;
|
||||
JSLinearString* style = value.toString()->ensureLinear(cx);
|
||||
if (!style)
|
||||
return nullptr;
|
||||
|
||||
if (StringEqualsAscii(style, "currency")) {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
|
||||
{
|
||||
JSLinearString* style = value.toString()->ensureLinear(cx);
|
||||
if (!style)
|
||||
return nullptr;
|
||||
currency = value.toString();
|
||||
MOZ_ASSERT(currency->length() == 3,
|
||||
"IsWellFormedCurrencyCode permits only length-3 strings");
|
||||
if (!stableChars.initTwoByte(cx, currency))
|
||||
return nullptr;
|
||||
// uCurrency remains owned by stableChars.
|
||||
uCurrency = stableChars.twoByteRange().begin().get();
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
|
||||
return nullptr;
|
||||
JSLinearString* currencyDisplay = value.toString()->ensureLinear(cx);
|
||||
if (!currencyDisplay)
|
||||
return nullptr;
|
||||
if (StringEqualsAscii(currencyDisplay, "code")) {
|
||||
uStyle = UNUM_CURRENCY_ISO;
|
||||
} else if (StringEqualsAscii(currencyDisplay, "symbol")) {
|
||||
uStyle = UNUM_CURRENCY;
|
||||
if (StringEqualsAscii(style, "currency")) {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
|
||||
return nullptr;
|
||||
currency = value.toString();
|
||||
MOZ_ASSERT(currency->length() == 3,
|
||||
"IsWellFormedCurrencyCode permits only length-3 strings");
|
||||
if (!stableChars.initTwoByte(cx, currency))
|
||||
return nullptr;
|
||||
// uCurrency remains owned by stableChars.
|
||||
uCurrency = stableChars.twoByteRange().begin().get();
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
|
||||
return nullptr;
|
||||
JSLinearString* currencyDisplay = value.toString()->ensureLinear(cx);
|
||||
if (!currencyDisplay)
|
||||
return nullptr;
|
||||
if (StringEqualsAscii(currencyDisplay, "code")) {
|
||||
uStyle = UNUM_CURRENCY_ISO;
|
||||
} else if (StringEqualsAscii(currencyDisplay, "symbol")) {
|
||||
uStyle = UNUM_CURRENCY;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(currencyDisplay, "name"));
|
||||
uStyle = UNUM_CURRENCY_PLURAL;
|
||||
}
|
||||
} else if (StringEqualsAscii(style, "percent")) {
|
||||
uStyle = UNUM_PERCENT;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(currencyDisplay, "name"));
|
||||
uStyle = UNUM_CURRENCY_PLURAL;
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "decimal"));
|
||||
uStyle = UNUM_DECIMAL;
|
||||
}
|
||||
} else if (StringEqualsAscii(style, "percent")) {
|
||||
uStyle = UNUM_PERCENT;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "decimal"));
|
||||
uStyle = UNUM_DECIMAL;
|
||||
}
|
||||
|
||||
bool hasP;
|
||||
@ -2483,8 +2490,8 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeForm
|
||||
PrivateValue(nullptr));
|
||||
|
||||
RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
return LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
|
||||
@ -3587,8 +3594,8 @@ PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
||||
pluralRules->setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
|
||||
pluralRules->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
||||
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
if (!IntlInitialize(cx, pluralRules, cx->names().InitializePluralRules, locales, options))
|
||||
@ -3660,6 +3667,8 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
|
||||
|
||||
double x = args[1].toNumber();
|
||||
|
||||
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
|
||||
if (!nf)
|
||||
return false;
|
||||
@ -3680,18 +3689,19 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
|
||||
return false;
|
||||
RootedLinearString type(cx, value.toString()->ensureLinear(cx));
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
double x = args[1].toNumber();
|
||||
|
||||
UPluralType category;
|
||||
if (StringEqualsAscii(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
{
|
||||
JSLinearString* type = value.toString()->ensureLinear(cx);
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
if (StringEqualsAscii(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Cache UPluralRules in PluralRulesObject::UPluralRulesSlot.
|
||||
@ -3835,7 +3845,7 @@ RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp)
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules"))
|
||||
if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat"))
|
||||
return false;
|
||||
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
@ -3961,6 +3971,8 @@ js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
RootedObject relativeTimeFormat(cx, &args[0].toObject());
|
||||
|
||||
double t = args[1].toNumber();
|
||||
|
||||
RootedObject internals(cx, GetInternals(cx, relativeTimeFormat));
|
||||
if (!internals)
|
||||
return false;
|
||||
@ -3975,46 +3987,47 @@ js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value))
|
||||
return false;
|
||||
RootedLinearString style(cx, value.toString()->ensureLinear(cx));
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
double t = args[1].toNumber();
|
||||
|
||||
UDateRelativeDateTimeFormatterStyle relDateTimeStyle;
|
||||
{
|
||||
JSLinearString* style = value.toString()->ensureLinear(cx);
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
if (StringEqualsAscii(style, "short")) {
|
||||
relDateTimeStyle = UDAT_STYLE_SHORT;
|
||||
} else if (StringEqualsAscii(style, "narrow")) {
|
||||
relDateTimeStyle = UDAT_STYLE_NARROW;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "long"));
|
||||
relDateTimeStyle = UDAT_STYLE_LONG;
|
||||
if (StringEqualsAscii(style, "short")) {
|
||||
relDateTimeStyle = UDAT_STYLE_SHORT;
|
||||
} else if (StringEqualsAscii(style, "narrow")) {
|
||||
relDateTimeStyle = UDAT_STYLE_NARROW;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "long"));
|
||||
relDateTimeStyle = UDAT_STYLE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
JSLinearString* unit = args[2].toString()->ensureLinear(cx);
|
||||
if (!unit)
|
||||
return false;
|
||||
|
||||
URelativeDateTimeUnit relDateTimeUnit;
|
||||
{
|
||||
JSLinearString* unit = args[2].toString()->ensureLinear(cx);
|
||||
if (!unit)
|
||||
return false;
|
||||
|
||||
if (StringEqualsAscii(unit, "second")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_SECOND;
|
||||
} else if (StringEqualsAscii(unit, "minute")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
|
||||
} else if (StringEqualsAscii(unit, "hour")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_HOUR;
|
||||
} else if (StringEqualsAscii(unit, "day")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_DAY;
|
||||
} else if (StringEqualsAscii(unit, "week")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_WEEK;
|
||||
} else if (StringEqualsAscii(unit, "month")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_MONTH;
|
||||
} else if (StringEqualsAscii(unit, "quarter")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(unit, "year"));
|
||||
relDateTimeUnit = UDAT_REL_UNIT_YEAR;
|
||||
if (StringEqualsAscii(unit, "second")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_SECOND;
|
||||
} else if (StringEqualsAscii(unit, "minute")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
|
||||
} else if (StringEqualsAscii(unit, "hour")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_HOUR;
|
||||
} else if (StringEqualsAscii(unit, "day")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_DAY;
|
||||
} else if (StringEqualsAscii(unit, "week")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_WEEK;
|
||||
} else if (StringEqualsAscii(unit, "month")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_MONTH;
|
||||
} else if (StringEqualsAscii(unit, "quarter")) {
|
||||
relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(unit, "year"));
|
||||
relDateTimeUnit = UDAT_REL_UNIT_YEAR;
|
||||
}
|
||||
}
|
||||
|
||||
// ICU doesn't handle -0 well: work around this by converting it to +0.
|
||||
@ -4034,8 +4047,8 @@ js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp)
|
||||
ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf);
|
||||
|
||||
JSString* str = Call(cx, [rtf, t, relDateTimeUnit](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return ureldatefmt_format(rtf, t, relDateTimeUnit, chars, size, status);
|
||||
});
|
||||
return ureldatefmt_format(rtf, t, relDateTimeUnit, chars, size, status);
|
||||
});
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
@ -4047,8 +4060,12 @@ js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp)
|
||||
/******************** String ********************/
|
||||
|
||||
static const char*
|
||||
CaseMappingLocale(JSLinearString* locale)
|
||||
CaseMappingLocale(JSContext* cx, JSString* str)
|
||||
{
|
||||
JSLinearString* locale = str->ensureLinear(cx);
|
||||
if (!locale)
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag");
|
||||
|
||||
// Lithuanian, Turkish, and Azeri have language dependent case mappings.
|
||||
@ -4070,12 +4087,6 @@ CaseMappingLocale(JSLinearString* locale)
|
||||
return ""; // ICU root locale
|
||||
}
|
||||
|
||||
static bool
|
||||
HasLanguageDependentCasing(JSLinearString* locale)
|
||||
{
|
||||
return !equal(CaseMappingLocale(locale), "");
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@ -4086,12 +4097,12 @@ js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
RootedString string(cx, args[0].toString());
|
||||
|
||||
RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
|
||||
const char* locale = CaseMappingLocale(cx, args[1].toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
// Call String.prototype.toLowerCase() for language independent casing.
|
||||
if (!HasLanguageDependentCasing(locale)) {
|
||||
if (equal(locale, "")) {
|
||||
JSString* str = js::StringToLowerCase(cx, string);
|
||||
if (!str)
|
||||
return false;
|
||||
@ -4109,9 +4120,8 @@ js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
|
||||
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
|
||||
"Case conversion doesn't overflow int32_t indices");
|
||||
|
||||
JSString* str = Call(cx, [&input, &locale](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return u_strToLower(chars, size, input.begin().get(), input.length(),
|
||||
CaseMappingLocale(locale), status);
|
||||
JSString* str = Call(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status);
|
||||
});
|
||||
if (!str)
|
||||
return false;
|
||||
@ -4130,12 +4140,12 @@ js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
RootedString string(cx, args[0].toString());
|
||||
|
||||
RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
|
||||
const char* locale = CaseMappingLocale(cx, args[1].toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
// Call String.prototype.toUpperCase() for language independent casing.
|
||||
if (!HasLanguageDependentCasing(locale)) {
|
||||
if (equal(locale, "")) {
|
||||
JSString* str = js::StringToUpperCase(cx, string);
|
||||
if (!str)
|
||||
return false;
|
||||
@ -4153,9 +4163,8 @@ js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
|
||||
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
|
||||
"Case conversion doesn't overflow int32_t indices");
|
||||
|
||||
JSString* str = Call(cx, [&input, &locale](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return u_strToUpper(chars, size, input.begin().get(), input.length(),
|
||||
CaseMappingLocale(locale), status);
|
||||
JSString* str = Call(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status);
|
||||
});
|
||||
if (!str)
|
||||
return false;
|
||||
@ -4492,27 +4501,27 @@ js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
RootedString str(cx);
|
||||
|
||||
// 1. Assert: locale is a string.
|
||||
str = args[0].toString();
|
||||
RootedString str(cx, args[0].toString());
|
||||
JSAutoByteString locale;
|
||||
if (!locale.encodeUtf8(cx, str))
|
||||
return false;
|
||||
|
||||
// 2. Assert: style is a string.
|
||||
JSLinearString* style = args[1].toString()->ensureLinear(cx);
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
DisplayNameStyle dnStyle;
|
||||
if (StringEqualsAscii(style, "narrow")) {
|
||||
dnStyle = DisplayNameStyle::Narrow;
|
||||
} else if (StringEqualsAscii(style, "short")) {
|
||||
dnStyle = DisplayNameStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "long"));
|
||||
dnStyle = DisplayNameStyle::Long;
|
||||
{
|
||||
JSLinearString* style = args[1].toString()->ensureLinear(cx);
|
||||
if (!style)
|
||||
return false;
|
||||
|
||||
if (StringEqualsAscii(style, "narrow")) {
|
||||
dnStyle = DisplayNameStyle::Narrow;
|
||||
} else if (StringEqualsAscii(style, "short")) {
|
||||
dnStyle = DisplayNameStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "long"));
|
||||
dnStyle = DisplayNameStyle::Long;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Assert: keys is an Array.
|
||||
|
@ -1680,12 +1680,10 @@ function InitializeCollator(collator, locales, options) {
|
||||
// Steps 4-5.
|
||||
//
|
||||
// If we ever need more speed here at startup, we should try to detect the
|
||||
// case where |options === undefined| and Object.prototype hasn't been
|
||||
// mucked with. (|options| is fully consumed in this method, so it's not a
|
||||
// concern that Object.prototype might be touched between now and when
|
||||
// |resolveCollatorInternals| is called.) For now, just keep it simple.
|
||||
// case where |options === undefined| and then directly use the default
|
||||
// value for each option. For now, just keep it simple.
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
@ -2175,12 +2173,10 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
|
||||
// Steps 4-5.
|
||||
//
|
||||
// If we ever need more speed here at startup, we should try to detect the
|
||||
// case where |options === undefined| and Object.prototype hasn't been
|
||||
// mucked with. (|options| is fully consumed in this method, so it's not a
|
||||
// concern that Object.prototype might be touched between now and when
|
||||
// |resolveNumberFormatInternals| is called.) For now just keep it simple.
|
||||
// case where |options === undefined| and then directly use the default
|
||||
// value for each option. For now, just keep it simple.
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
@ -3372,7 +3368,7 @@ function InitializePluralRules(pluralRules, locales, options) {
|
||||
|
||||
// Steps 4-5.
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
@ -3593,7 +3589,7 @@ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
|
||||
|
||||
// Steps 4-5.
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
@ -3805,8 +3801,8 @@ function Intl_getDisplayNames(locales, options) {
|
||||
|
||||
// 2. If options is undefined, then
|
||||
if (options === undefined)
|
||||
// a. Let options be ObjectCreate(%ObjectPrototype%).
|
||||
options = {};
|
||||
// a. Let options be ObjectCreate(null).
|
||||
options = std_Object_create(null);
|
||||
// 3. Else,
|
||||
else
|
||||
// a. Let options be ? ToObject(options).
|
||||
|
@ -702,11 +702,6 @@ class MemoryCounter
|
||||
return triggered_;
|
||||
}
|
||||
|
||||
void decrement(size_t bytes) {
|
||||
MOZ_ASSERT(bytes <= bytes_);
|
||||
bytes_ -= bytes;
|
||||
}
|
||||
|
||||
void adopt(MemoryCounter<T>& other) {
|
||||
bytes_ += other.bytes();
|
||||
other.reset();
|
||||
|
@ -449,8 +449,7 @@ struct Zone : public JS::shadow::Zone,
|
||||
gcMallocCounter.setMax(value, lock);
|
||||
}
|
||||
void updateMallocCounter(size_t nbytes) {
|
||||
if (!runtime_->gc.updateMallocCounter(nbytes))
|
||||
gcMallocCounter.update(this, nbytes);
|
||||
gcMallocCounter.update(this, nbytes);
|
||||
}
|
||||
void adoptMallocBytes(Zone* other) {
|
||||
gcMallocCounter.adopt(other->gcMallocCounter);
|
||||
|
@ -6,10 +6,11 @@ Object.prototype.localeMatcher = "invalid matcher option";
|
||||
|
||||
// The Intl API may not be available in the testing environment.
|
||||
if (this.hasOwnProperty("Intl")) {
|
||||
// Intl constructors no longer work properly, because "localeMatcher" defaults to the invalid
|
||||
// value from Object.prototype. Except for Intl.DateTimeFormat, cf. ECMA-402 ToDateTimeOptions.
|
||||
assertThrowsInstanceOf(() => new Intl.Collator(), RangeError);
|
||||
assertThrowsInstanceOf(() => new Intl.NumberFormat(), RangeError);
|
||||
// Intl constructors still work perfectly fine. The default options object doesn't inherit
|
||||
// from Object.prototype and the invalid "localeMatcher" value from Object.prototype isn't
|
||||
// consulted.
|
||||
new Intl.Collator().compare("a", "b");
|
||||
new Intl.NumberFormat().format(10);
|
||||
new Intl.DateTimeFormat().format(new Date);
|
||||
|
||||
// If an explicit "localeMatcher" option is given, the default from Object.prototype is ignored.
|
||||
@ -19,7 +20,7 @@ if (this.hasOwnProperty("Intl")) {
|
||||
|
||||
delete Object.prototype.localeMatcher;
|
||||
|
||||
// After removing the default option from Object.prototype, everything works again as expected.
|
||||
// After removing the default option from Object.prototype, everything still works as expected.
|
||||
new Intl.Collator().compare("a", "b");
|
||||
new Intl.NumberFormat().format(10);
|
||||
new Intl.DateTimeFormat().format(new Date);
|
||||
|
@ -4337,21 +4337,15 @@ GCRuntime::updateMallocCountersOnGC()
|
||||
{
|
||||
AutoLockGC lock(rt);
|
||||
|
||||
size_t totalBytesInCollectedZones = 0;
|
||||
// Update the malloc counters for any zones we are collecting.
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
if (zone->isCollecting()) {
|
||||
totalBytesInCollectedZones += zone->GCMallocBytes();
|
||||
if (zone->isCollecting())
|
||||
zone->updateGCMallocBytesOnGC(lock);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the runtime malloc counter. If we are doing a full GC then clear
|
||||
// it, otherwise decrement it by the previous malloc bytes count for the
|
||||
// zones we did collect.
|
||||
// Update the runtime malloc counter only if we are doing a full GC.
|
||||
if (isFull)
|
||||
mallocCounter.updateOnGC(lock);
|
||||
else
|
||||
mallocCounter.decrement(totalBytesInCollectedZones);
|
||||
}
|
||||
|
||||
template <class ZoneIterT>
|
||||
|
@ -468,12 +468,6 @@ skip include test262/built-ins/RegExp/lookBehind/jstests.list
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1362154
|
||||
skip include test262/built-ins/RegExp/named-groups/jstests.list
|
||||
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1398185
|
||||
skip script test262/intl402/Collator/default-options-object-prototype.js
|
||||
skip script test262/intl402/Number/prototype/toLocaleString/default-options-object-prototype.js
|
||||
skip script test262/intl402/String/prototype/localeCompare/default-options-object-prototype.js
|
||||
skip script test262/intl402/NumberFormat/default-options-object-prototype.js
|
||||
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1386146
|
||||
skip script test262/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle.js
|
||||
|
||||
|
12
layout/generic/crashtests/1375858.html
Normal file
12
layout/generic/crashtests/1375858.html
Normal file
@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
*::-moz-list-bullet, * {
|
||||
transform-style:preserve-3d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<li></li>
|
||||
</body>
|
||||
</html>
|
@ -662,6 +662,7 @@ load 1364361-1.html
|
||||
load 1367413-1.html
|
||||
load 1368617-1.html
|
||||
load 1373586.html
|
||||
load 1375858.html
|
||||
load 1381134.html
|
||||
load 1381134-2.html
|
||||
load 1401420-1.html
|
||||
|
@ -87,6 +87,14 @@ public:
|
||||
void AddInlinePrefISize(gfxContext* aRenderingContext,
|
||||
nsIFrame::InlinePrefISizeData* aData) override;
|
||||
|
||||
virtual bool IsFrameOfType(uint32_t aFlags) const override
|
||||
{
|
||||
if (aFlags & eSupportsCSSTransforms) {
|
||||
return false;
|
||||
}
|
||||
return nsFrame::IsFrameOfType(aFlags);
|
||||
}
|
||||
|
||||
// nsBulletFrame
|
||||
int32_t SetListItemOrdinal(int32_t aNextOrdinal, bool* aChanged,
|
||||
int32_t aIncrement);
|
||||
|
@ -2511,16 +2511,15 @@ nsFrameSelection::UnselectCells(nsIContent *aTableContent,
|
||||
nsTableCellFrame* cellFrame =
|
||||
tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
|
||||
|
||||
int32_t origRowIndex, origColIndex;
|
||||
cellFrame->GetRowIndex(origRowIndex);
|
||||
cellFrame->GetColIndex(origColIndex);
|
||||
uint32_t origRowIndex = cellFrame->RowIndex();
|
||||
uint32_t origColIndex = cellFrame->ColIndex();
|
||||
uint32_t actualRowSpan =
|
||||
tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
|
||||
uint32_t actualColSpan =
|
||||
tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
|
||||
if (origRowIndex <= maxRowIndex && maxRowIndex >= 0 &&
|
||||
if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) && maxRowIndex >= 0 &&
|
||||
origRowIndex + actualRowSpan - 1 >= static_cast<uint32_t>(minRowIndex) &&
|
||||
origColIndex <= maxColIndex && maxColIndex >= 0 &&
|
||||
origColIndex <= static_cast<uint32_t>(maxColIndex) && maxColIndex >= 0 &&
|
||||
origColIndex + actualColSpan - 1 >= static_cast<uint32_t>(minColIndex)) {
|
||||
|
||||
mDomSelections[index]->RemoveRange(range);
|
||||
@ -2554,33 +2553,32 @@ nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent,
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsresult result = NS_OK;
|
||||
int32_t row = aStartRowIndex;
|
||||
uint32_t row = aStartRowIndex;
|
||||
while(true)
|
||||
{
|
||||
int32_t col = aStartColumnIndex;
|
||||
uint32_t col = aStartColumnIndex;
|
||||
while(true)
|
||||
{
|
||||
nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
|
||||
|
||||
// Skip cells that are spanned from previous locations or are already selected
|
||||
if (cellFrame) {
|
||||
int32_t origRow, origCol;
|
||||
cellFrame->GetRowIndex(origRow);
|
||||
cellFrame->GetColIndex(origCol);
|
||||
uint32_t origRow = cellFrame->RowIndex();
|
||||
uint32_t origCol = cellFrame->ColIndex();
|
||||
if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
|
||||
result = SelectCellElement(cellFrame->GetContent());
|
||||
if (NS_FAILED(result)) return result;
|
||||
}
|
||||
}
|
||||
// Done when we reach end column
|
||||
if (col == aEndColumnIndex) break;
|
||||
if (col == static_cast<uint32_t>(aEndColumnIndex)) break;
|
||||
|
||||
if (aStartColumnIndex < aEndColumnIndex)
|
||||
col ++;
|
||||
else
|
||||
col--;
|
||||
}
|
||||
if (row == aEndRowIndex) break;
|
||||
if (row == static_cast<uint32_t>(aEndRowIndex)) break;
|
||||
|
||||
if (aStartRowIndex < aEndRowIndex)
|
||||
row++;
|
||||
|
@ -183,10 +183,8 @@ static void
|
||||
ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame,
|
||||
nsStyleBorder& aStyleBorder)
|
||||
{
|
||||
int32_t rowIndex;
|
||||
int32_t columnIndex;
|
||||
aFrame->GetRowIndex(rowIndex);
|
||||
aFrame->GetColIndex(columnIndex);
|
||||
uint32_t rowIndex = aFrame->RowIndex();
|
||||
uint32_t columnIndex = aFrame->ColIndex();
|
||||
|
||||
nscoord borderWidth =
|
||||
nsPresContext::GetBorderWidthForKeyword(NS_STYLE_BORDER_WIDTH_THIN);
|
||||
@ -201,7 +199,7 @@ ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame,
|
||||
if (rowIndex > 0 && rowLinesList) {
|
||||
// If the row number is greater than the number of provided rowline
|
||||
// values, we simply repeat the last value.
|
||||
int32_t listLength = rowLinesList->Length();
|
||||
uint32_t listLength = rowLinesList->Length();
|
||||
if (rowIndex < listLength) {
|
||||
aStyleBorder.SetBorderStyle(eSideTop,
|
||||
rowLinesList->ElementAt(rowIndex - 1));
|
||||
@ -216,7 +214,7 @@ ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame,
|
||||
if (columnIndex > 0 && columnLinesList) {
|
||||
// If the column number is greater than the number of provided columline
|
||||
// values, we simply repeat the last value.
|
||||
int32_t listLength = columnLinesList->Length();
|
||||
uint32_t listLength = columnLinesList->Length();
|
||||
if (columnIndex < listLength) {
|
||||
aStyleBorder.SetBorderStyle(eSideLeft,
|
||||
columnLinesList->ElementAt(columnIndex - 1));
|
||||
@ -1206,12 +1204,11 @@ nsMathMLmtdFrame::GetVerticalAlign() const
|
||||
nsTArray<int8_t>* alignmentList = FindCellProperty(this, RowAlignProperty());
|
||||
|
||||
if (alignmentList) {
|
||||
int32_t rowIndex;
|
||||
GetRowIndex(rowIndex);
|
||||
uint32_t rowIndex = RowIndex();
|
||||
|
||||
// If the row number is greater than the number of provided rowalign values,
|
||||
// we simply repeat the last value.
|
||||
if (rowIndex < (int32_t)alignmentList->Length())
|
||||
if (rowIndex < alignmentList->Length())
|
||||
alignment = alignmentList->ElementAt(rowIndex);
|
||||
else
|
||||
alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
|
||||
@ -1298,12 +1295,11 @@ nsStyleText* nsMathMLmtdInnerFrame::StyleTextForLineLayout()
|
||||
|
||||
if (alignmentList) {
|
||||
nsMathMLmtdFrame* cellFrame = (nsMathMLmtdFrame*)GetParent();
|
||||
int32_t columnIndex;
|
||||
cellFrame->GetColIndex(columnIndex);
|
||||
uint32_t columnIndex = cellFrame->ColIndex();
|
||||
|
||||
// If the column number is greater than the number of provided columalign
|
||||
// values, we simply repeat the last value.
|
||||
if (columnIndex < (int32_t)alignmentList->Length())
|
||||
if (columnIndex < alignmentList->Length())
|
||||
alignment = alignmentList->ElementAt(columnIndex);
|
||||
else
|
||||
alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
|
||||
|
@ -1101,13 +1101,6 @@ public:
|
||||
* the child layers.
|
||||
*/
|
||||
void ProcessDisplayItems(nsDisplayList* aList);
|
||||
void ProcessDisplayItems(nsDisplayList* aList,
|
||||
AnimatedGeometryRoot* aLastAnimatedGeometryRoot,
|
||||
const ActiveScrolledRoot* aLastASR,
|
||||
const nsPoint& aLastAGRTopLeft,
|
||||
nsPoint& aTopLeft,
|
||||
int32_t aMaxLayers,
|
||||
int& aLayerCount);
|
||||
/**
|
||||
* This finalizes all the open PaintedLayers by popping every element off
|
||||
* mPaintedLayerDataStack, then sets the children of the container layer
|
||||
@ -3983,20 +3976,8 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList)
|
||||
int32_t maxLayers = gfxPrefs::MaxActiveLayers();
|
||||
int layerCount = 0;
|
||||
|
||||
ProcessDisplayItems(aList, lastAnimatedGeometryRoot, lastASR,
|
||||
lastAGRTopLeft, topLeft, maxLayers, layerCount);
|
||||
}
|
||||
|
||||
void
|
||||
ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
AnimatedGeometryRoot* aLastAnimatedGeometryRoot,
|
||||
const ActiveScrolledRoot* aLastASR,
|
||||
const nsPoint& aLastAGRTopLeft,
|
||||
nsPoint& aTopLeft,
|
||||
int32_t aMaxLayers,
|
||||
int& aLayerCount)
|
||||
{
|
||||
for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
|
||||
FlattenedDisplayItemIterator iter(mBuilder, aList);
|
||||
while (nsDisplayItem* i = iter.GetNext()) {
|
||||
nsDisplayItem* item = i;
|
||||
MOZ_ASSERT(item);
|
||||
|
||||
@ -4017,7 +3998,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
// item. We create a list of consecutive items that can be merged together.
|
||||
AutoTArray<nsDisplayItem*, 1> mergedItems;
|
||||
mergedItems.AppendElement(item);
|
||||
for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
|
||||
while (nsDisplayItem* peek = iter.PeekNext()) {
|
||||
if (!item->CanMerge(peek)) {
|
||||
break;
|
||||
}
|
||||
@ -4025,7 +4006,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
mergedItems.AppendElement(peek);
|
||||
|
||||
// Move the iterator forward since we will merge this item.
|
||||
i = peek;
|
||||
i = iter.GetNext();
|
||||
}
|
||||
|
||||
if (mergedItems.Length() > 1) {
|
||||
@ -4035,18 +4016,6 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
MOZ_ASSERT(item && itemType == item->GetType());
|
||||
}
|
||||
|
||||
nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
|
||||
|
||||
if (item->ShouldFlattenAway(mBuilder)) {
|
||||
MOZ_ASSERT(childItems);
|
||||
ProcessDisplayItems(childItems, aLastAnimatedGeometryRoot, aLastASR,
|
||||
aLastAGRTopLeft, aTopLeft, aMaxLayers, aLayerCount);
|
||||
if (childItems->NeedsTransparentSurface()) {
|
||||
aList->SetNeedsTransparentSurface();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(item->GetType() != DisplayItemType::TYPE_WRAP_LIST);
|
||||
|
||||
NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
|
||||
@ -4074,9 +4043,9 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
const DisplayItemClipChain* layerClipChain = nullptr;
|
||||
if (mFlattenToSingleLayer && layerState != LAYER_ACTIVE_FORCE) {
|
||||
forceInactive = true;
|
||||
animatedGeometryRoot = aLastAnimatedGeometryRoot;
|
||||
itemASR = aLastASR;
|
||||
aTopLeft = aLastAGRTopLeft;
|
||||
animatedGeometryRoot = lastAnimatedGeometryRoot;
|
||||
itemASR = lastASR;
|
||||
topLeft = lastAGRTopLeft;
|
||||
item->FuseClipChainUpTo(mBuilder, mContainerASR);
|
||||
} else {
|
||||
forceInactive = false;
|
||||
@ -4098,7 +4067,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
itemASR = mContainerASR;
|
||||
item->FuseClipChainUpTo(mBuilder, mContainerASR);
|
||||
}
|
||||
aTopLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
|
||||
topLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
|
||||
}
|
||||
|
||||
const ActiveScrolledRoot* scrollMetadataASR =
|
||||
@ -4155,7 +4124,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
ScaleToOutsidePixels(item->GetVisibleRect(), false));
|
||||
}
|
||||
|
||||
if (aMaxLayers != -1 && aLayerCount >= aMaxLayers) {
|
||||
if (maxLayers != -1 && layerCount >= maxLayers) {
|
||||
forceInactive = true;
|
||||
}
|
||||
|
||||
@ -4166,7 +4135,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
(layerState == LAYER_ACTIVE_EMPTY ||
|
||||
layerState == LAYER_ACTIVE))) {
|
||||
|
||||
aLayerCount++;
|
||||
layerCount++;
|
||||
|
||||
// LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
|
||||
// We should never see an empty layer with any visible content!
|
||||
@ -4371,11 +4340,11 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
nsDisplayMask* maskItem = static_cast<nsDisplayMask*>(item);
|
||||
SetupMaskLayerForCSSMask(ownLayer, maskItem);
|
||||
|
||||
if (i->GetAbove() &&
|
||||
i->GetAbove()->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
|
||||
if (iter.PeekNext() &&
|
||||
iter.PeekNext()->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
|
||||
// Since we do build a layer for mask, there is no need for this
|
||||
// scroll info layer anymore.
|
||||
i = i->GetAbove();
|
||||
i = iter.GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4484,7 +4453,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
item->Frame()->In3DContextAndBackfaceIsHidden(),
|
||||
[&]() {
|
||||
return NewPaintedLayerData(item, animatedGeometryRoot, itemASR, layerClipChain, scrollMetadataASR,
|
||||
aTopLeft);
|
||||
topLeft);
|
||||
});
|
||||
|
||||
if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
|
||||
@ -4502,7 +4471,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
if (!paintedLayerData->mLayer) {
|
||||
// Try to recycle the old layer of this display item.
|
||||
RefPtr<PaintedLayer> layer =
|
||||
AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, aTopLeft);
|
||||
AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, topLeft);
|
||||
if (layer) {
|
||||
paintedLayerData->mLayer = layer;
|
||||
|
||||
@ -4514,6 +4483,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList,
|
||||
}
|
||||
}
|
||||
|
||||
nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
|
||||
if (childItems && childItems->NeedsTransparentSurface()) {
|
||||
aList->SetNeedsTransparentSurface();
|
||||
}
|
||||
|
@ -1100,11 +1100,14 @@ void nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame,
|
||||
// viewport, or the scroll position clamping scrollport size, if one is
|
||||
// set.
|
||||
nsIPresShell* ps = aFrame->PresContext()->PresShell();
|
||||
dirtyRectRelativeToDirtyFrame.MoveTo(0, 0);
|
||||
if (ps->IsScrollPositionClampingScrollPortSizeSet()) {
|
||||
dirtyRectRelativeToDirtyFrame.SizeTo(ps->GetScrollPositionClampingScrollPortSize());
|
||||
dirtyRectRelativeToDirtyFrame =
|
||||
nsRect(nsPoint(0, 0), ps->GetScrollPositionClampingScrollPortSize());
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
} else {
|
||||
dirtyRectRelativeToDirtyFrame.SizeTo(aDirtyFrame->GetSize());
|
||||
dirtyRectRelativeToDirtyFrame =
|
||||
nsRect(nsPoint(0, 0), aDirtyFrame->GetSize());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
nsPoint offset = aFrame->GetOffsetTo(aDirtyFrame);
|
||||
@ -6141,6 +6144,13 @@ CollectItemsWithOpacity(nsDisplayList* aList,
|
||||
bool
|
||||
nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
|
||||
{
|
||||
if (mFrame->GetPrevContinuation() ||
|
||||
mFrame->GetNextContinuation()) {
|
||||
// If we've been split, then we might need to merge, so
|
||||
// don't flatten us away.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeedsActiveLayer(aBuilder, mFrame) || mOpacity == 0.0) {
|
||||
// If our opacity is zero then we'll discard all descendant display items
|
||||
// except for layer event regions, so there's no point in doing this
|
||||
|
@ -5588,6 +5588,73 @@ public:
|
||||
mutable mozilla::Maybe<bool> mIsFrameSelected;
|
||||
};
|
||||
|
||||
class FlattenedDisplayItemIterator
|
||||
{
|
||||
public:
|
||||
FlattenedDisplayItemIterator(nsDisplayListBuilder* aBuilder,
|
||||
nsDisplayList* aList)
|
||||
: mBuilder(aBuilder)
|
||||
, mNext(aList->GetBottom())
|
||||
{
|
||||
ResolveFlattening();
|
||||
}
|
||||
|
||||
nsDisplayItem* GetNext()
|
||||
{
|
||||
nsDisplayItem* next = mNext;
|
||||
|
||||
// Advance mNext to the following item
|
||||
if (next) {
|
||||
mNext = mNext->GetAbove();
|
||||
ResolveFlattening();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
nsDisplayItem* PeekNext()
|
||||
{
|
||||
return mNext;
|
||||
}
|
||||
|
||||
private:
|
||||
bool AtEndOfNestedList()
|
||||
{
|
||||
return !mNext && mStack.Length() > 0;
|
||||
}
|
||||
|
||||
bool ShouldFlattenNextItem()
|
||||
{
|
||||
return mNext && mNext->ShouldFlattenAway(mBuilder);
|
||||
}
|
||||
|
||||
void ResolveFlattening()
|
||||
{
|
||||
// Handle the case where we reach the end of a nested list, or the current
|
||||
// item should start a new nested list. Repeat this until we find an actual
|
||||
// item, or the very end of the outer list.
|
||||
while (AtEndOfNestedList() || ShouldFlattenNextItem()) {
|
||||
if (AtEndOfNestedList()) {
|
||||
// Pop the last item off the stack.
|
||||
mNext = mStack.LastElement();
|
||||
mStack.RemoveElementAt(mStack.Length() - 1);
|
||||
// We stored the item that was flattened, so advance to the next.
|
||||
mNext = mNext->GetAbove();
|
||||
} else {
|
||||
// This item wants to be flattened. Store the current item on the stack,
|
||||
// and use the first item in the child list instead.
|
||||
mStack.AppendElement(mNext);
|
||||
nsDisplayList* childItems = mNext->GetSameCoordinateSystemChildren();
|
||||
mNext = childItems->GetBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsDisplayListBuilder* mBuilder;
|
||||
nsDisplayItem* mNext;
|
||||
AutoTArray<nsDisplayItem*, 10> mStack;
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class PaintTelemetry
|
||||
|
@ -1687,7 +1687,7 @@ fuzzy-if(skiaContent,1,4500) == 654950-1.html 654950-1-ref.html # Quartz alpha b
|
||||
== 655836-1.html 655836-1-ref.html
|
||||
!= 656875.html about:blank
|
||||
== 658952.html 658952-ref.html
|
||||
fuzzy-if(skiaContent,1,3500) == 660682-1.html 660682-1-ref.html
|
||||
fuzzy-if(skiaContent,7,3500) fails-if(webrender) == 660682-1.html 660682-1-ref.html
|
||||
fuzzy-if(d2d,1,256) skip-if(Android) fuzzy-if(skiaContent,1,68000) fails-if(styloVsGecko) asserts-if(stylo,16-18) == 664127-1.xul 664127-1-ref.xul # Android: Intermittent failures - bug 1019131, stylo: bug 1397644
|
||||
== 665597-1.html 665597-1-ref.html
|
||||
== 665597-2.html 665597-2-ref.html
|
||||
|
@ -274,7 +274,7 @@ nsDocumentRuleResultCacheKey::Matches(
|
||||
|
||||
#ifdef DEBUG
|
||||
for (css::DocumentRule* rule : mMatchingRules) {
|
||||
MOZ_ASSERT(aRules.BinaryIndexOf(rule) != aRules.NoIndex,
|
||||
MOZ_ASSERT(aRules.ContainsSorted(rule),
|
||||
"aRules must contain all rules in mMatchingRules");
|
||||
}
|
||||
#endif
|
||||
|
@ -2431,9 +2431,8 @@ void nsCellMap::Dump(bool aIsBorderCollapse) const
|
||||
if (cd) {
|
||||
if (cd->IsOrig()) {
|
||||
nsTableCellFrame* cellFrame = cd->GetCellFrame();
|
||||
int32_t cellFrameColIndex;
|
||||
cellFrame->GetColIndex(cellFrameColIndex);
|
||||
printf("C%d,%d=%p(%d) ", rIndex, colIndex, (void*)cellFrame,
|
||||
uint32_t cellFrameColIndex = cellFrame->ColIndex();
|
||||
printf("C%d,%d=%p(%u) ", rIndex, colIndex, (void*)cellFrame,
|
||||
cellFrameColIndex);
|
||||
cellCount++;
|
||||
}
|
||||
@ -2520,8 +2519,7 @@ nsCellMap::GetCellInfoAt(const nsTableCellMap& aMap,
|
||||
cellFrame = GetCellFrame(aRowX, aColX, *data, true);
|
||||
}
|
||||
if (cellFrame && aColSpan) {
|
||||
int32_t initialColIndex;
|
||||
cellFrame->GetColIndex(initialColIndex);
|
||||
uint32_t initialColIndex = cellFrame->ColIndex();
|
||||
*aColSpan = GetEffectiveColSpan(aMap, aRowX, initialColIndex);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
/**
|
||||
* nsITableCellLayout
|
||||
* interface for layout objects that act like table cells.
|
||||
* XXXbz This interface should really go away...
|
||||
*
|
||||
* @author sclark
|
||||
*/
|
||||
@ -25,12 +26,6 @@ public:
|
||||
|
||||
/** return the mapped cell's row and column indexes (starting at 0 for each) */
|
||||
NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex)=0;
|
||||
|
||||
/** return the mapped cell's row index (starting at 0 for the first row) */
|
||||
virtual nsresult GetRowIndex(int32_t &aRowIndex) const = 0;
|
||||
|
||||
/** return the mapped cell's column index (starting at 0 for the first column) */
|
||||
virtual nsresult GetColIndex(int32_t &aColIndex) const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -60,20 +60,6 @@ nsTableCellFrame::~nsTableCellFrame()
|
||||
|
||||
NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame)
|
||||
|
||||
nsTableCellFrame*
|
||||
nsTableCellFrame::GetNextCell() const
|
||||
{
|
||||
nsIFrame* childFrame = GetNextSibling();
|
||||
while (childFrame) {
|
||||
nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
|
||||
if (cellFrame) {
|
||||
return cellFrame;
|
||||
}
|
||||
childFrame = childFrame->GetNextSibling();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
nsTableCellFrame::Init(nsIContent* aContent,
|
||||
nsContainerFrame* aParent,
|
||||
@ -89,8 +75,7 @@ nsTableCellFrame::Init(nsIContent* aContent,
|
||||
if (aPrevInFlow) {
|
||||
// Set the column index
|
||||
nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow;
|
||||
int32_t colIndex;
|
||||
cellFrame->GetColIndex(colIndex);
|
||||
uint32_t colIndex = cellFrame->ColIndex();
|
||||
SetColIndex(colIndex);
|
||||
} else {
|
||||
// Although the spec doesn't say that writing-mode is not applied to
|
||||
@ -190,34 +175,6 @@ nsTableCellFrame::NeedsToObserve(const ReflowInput& aReflowInput)
|
||||
fType == LayoutFrameType::TableWrapper);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTableCellFrame::GetRowIndex(int32_t &aRowIndex) const
|
||||
{
|
||||
nsresult result;
|
||||
nsTableRowFrame* row = static_cast<nsTableRowFrame*>(GetParent());
|
||||
if (row) {
|
||||
aRowIndex = row->GetRowIndex();
|
||||
result = NS_OK;
|
||||
}
|
||||
else {
|
||||
aRowIndex = 0;
|
||||
result = NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTableCellFrame::GetColIndex(int32_t &aColIndex) const
|
||||
{
|
||||
if (GetPrevInFlow()) {
|
||||
return static_cast<nsTableCellFrame*>(FirstInFlow())->GetColIndex(aColIndex);
|
||||
}
|
||||
else {
|
||||
aColIndex = mColIndex;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID,
|
||||
nsAtom* aAttribute,
|
||||
@ -250,13 +207,13 @@ nsTableCellFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
|
||||
nsTableFrame* tableFrame = GetTableFrame();
|
||||
if (tableFrame->IsBorderCollapse() &&
|
||||
tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
|
||||
int32_t colIndex, rowIndex;
|
||||
GetColIndex(colIndex);
|
||||
GetRowIndex(rowIndex);
|
||||
uint32_t colIndex = ColIndex();
|
||||
uint32_t rowIndex = RowIndex();
|
||||
// row span needs to be clamped as we do not create rows in the cellmap
|
||||
// which do not have cells originating in them
|
||||
TableArea damageArea(colIndex, rowIndex, GetColSpan(),
|
||||
std::min(GetRowSpan(), tableFrame->GetRowCount() - rowIndex));
|
||||
std::min(static_cast<uint32_t>(GetRowSpan()),
|
||||
tableFrame->GetRowCount() - rowIndex));
|
||||
tableFrame->AddBCDamageArea(damageArea);
|
||||
}
|
||||
}
|
||||
@ -842,14 +799,13 @@ CalcUnpaginatedBSize(nsTableCellFrame& aCellFrame,
|
||||
nsTableRowGroupFrame* firstRGInFlow =
|
||||
static_cast<nsTableRowGroupFrame*>(row->GetParent());
|
||||
|
||||
int32_t rowIndex;
|
||||
firstCellInFlow->GetRowIndex(rowIndex);
|
||||
uint32_t rowIndex = firstCellInFlow->RowIndex();
|
||||
int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow);
|
||||
|
||||
nscoord computedBSize = firstTableInFlow->GetRowSpacing(rowIndex,
|
||||
rowIndex + rowSpan - 1);
|
||||
computedBSize -= aBlockDirBorderPadding;
|
||||
int32_t rowX;
|
||||
uint32_t rowX;
|
||||
for (row = firstRGInFlow->GetFirstRow(), rowX = 0; row; row = row->GetNextRow(), rowX++) {
|
||||
if (rowX > rowIndex + rowSpan - 1) {
|
||||
break;
|
||||
@ -1065,12 +1021,7 @@ nsTableCellFrame::AccessibleType()
|
||||
NS_IMETHODIMP
|
||||
nsTableCellFrame::GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex)
|
||||
{
|
||||
nsresult res = GetRowIndex(aRowIndex);
|
||||
if (NS_FAILED(res))
|
||||
{
|
||||
aColIndex = 0;
|
||||
return res;
|
||||
}
|
||||
aRowIndex = RowIndex();
|
||||
aColIndex = mColIndex;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -170,7 +170,10 @@ public:
|
||||
NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex) override;
|
||||
|
||||
/** return the mapped cell's row index (starting at 0 for the first row) */
|
||||
virtual nsresult GetRowIndex(int32_t &aRowIndex) const override;
|
||||
uint32_t RowIndex() const
|
||||
{
|
||||
return static_cast<nsTableRowFrame*>(GetParent())->GetRowIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the cell's specified col span. this is what was specified in the
|
||||
@ -181,7 +184,17 @@ public:
|
||||
int32_t GetColSpan();
|
||||
|
||||
/** return the cell's column index (starting at 0 for the first column) */
|
||||
virtual nsresult GetColIndex(int32_t &aColIndex) const override;
|
||||
uint32_t ColIndex() const
|
||||
{
|
||||
// NOTE: We copy this from previous continuations, and we don't ever have
|
||||
// dynamic updates when tables split, so our mColIndex always matches our
|
||||
// first continuation's.
|
||||
MOZ_ASSERT(static_cast<nsTableCellFrame*>(FirstContinuation())->mColIndex ==
|
||||
mColIndex,
|
||||
"mColIndex out of sync with first continuation");
|
||||
return mColIndex;
|
||||
}
|
||||
|
||||
void SetColIndex(int32_t aColIndex);
|
||||
|
||||
/** return the available isize given to this frame during its last reflow */
|
||||
@ -202,7 +215,17 @@ public:
|
||||
bool HasPctOverBSize();
|
||||
void SetHasPctOverBSize(bool aValue);
|
||||
|
||||
nsTableCellFrame* GetNextCell() const;
|
||||
nsTableCellFrame* GetNextCell() const
|
||||
{
|
||||
nsIFrame* sibling = GetNextSibling();
|
||||
#ifdef DEBUG
|
||||
if (sibling) {
|
||||
nsTableCellFrame* cellFrame = do_QueryFrame(sibling);
|
||||
MOZ_ASSERT(cellFrame, "How do we have a non-cell sibling?");
|
||||
}
|
||||
#endif // DEBUG
|
||||
return static_cast<nsTableCellFrame*>(sibling);
|
||||
}
|
||||
|
||||
virtual LogicalMargin GetBorderWidth(WritingMode aWM) const;
|
||||
|
||||
@ -339,4 +362,17 @@ private:
|
||||
BCPixelSize mIStartBorder;
|
||||
};
|
||||
|
||||
// Implemented here because that's a sane-ish way to make the includes work out.
|
||||
inline nsTableCellFrame* nsTableRowFrame::GetFirstCell() const
|
||||
{
|
||||
nsIFrame* firstChild = mFrames.FirstChild();
|
||||
#ifdef DEBUG
|
||||
if (firstChild) {
|
||||
nsTableCellFrame* cellFrame = do_QueryFrame(firstChild);
|
||||
MOZ_ASSERT(cellFrame, "How do we have a non-cell sibling?");
|
||||
}
|
||||
#endif // DEBUG
|
||||
return static_cast<nsTableCellFrame*>(firstChild);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -364,9 +364,8 @@ nsTableFrame::RowOrColSpanChanged(nsTableCellFrame* aCellFrame)
|
||||
nsTableCellMap* cellMap = GetCellMap();
|
||||
if (cellMap) {
|
||||
// for now just remove the cell from the map and reinsert it
|
||||
int32_t rowIndex, colIndex;
|
||||
aCellFrame->GetRowIndex(rowIndex);
|
||||
aCellFrame->GetColIndex(colIndex);
|
||||
uint32_t rowIndex = aCellFrame->RowIndex();
|
||||
uint32_t colIndex = aCellFrame->ColIndex();
|
||||
RemoveCell(aCellFrame, rowIndex);
|
||||
AutoTArray<nsTableCellFrame*, 1> cells;
|
||||
cells.AppendElement(aCellFrame);
|
||||
@ -442,9 +441,7 @@ nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
|
||||
nsTableCellMap* cellMap = GetCellMap();
|
||||
NS_PRECONDITION (nullptr != cellMap, "bad call, cellMap not yet allocated.");
|
||||
|
||||
int32_t colIndex;
|
||||
aCell.GetColIndex(colIndex);
|
||||
return cellMap->GetEffectiveRowSpan(aRowIndex, colIndex);
|
||||
return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex());
|
||||
}
|
||||
|
||||
int32_t
|
||||
@ -453,9 +450,8 @@ nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell,
|
||||
{
|
||||
nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1);
|
||||
|
||||
int32_t colIndex, rowIndex;
|
||||
aCell.GetColIndex(colIndex);
|
||||
aCell.GetRowIndex(rowIndex);
|
||||
uint32_t colIndex = aCell.ColIndex();
|
||||
uint32_t rowIndex = aCell.RowIndex();
|
||||
|
||||
if (aCellMap)
|
||||
return aCellMap->GetRowSpan(rowIndex, colIndex, true);
|
||||
@ -469,9 +465,8 @@ nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell,
|
||||
{
|
||||
nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1);
|
||||
|
||||
int32_t colIndex, rowIndex;
|
||||
aCell.GetColIndex(colIndex);
|
||||
aCell.GetRowIndex(rowIndex);
|
||||
uint32_t colIndex = aCell.ColIndex();
|
||||
uint32_t rowIndex = aCell.RowIndex();
|
||||
|
||||
if (aCellMap)
|
||||
return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex);
|
||||
@ -1403,33 +1398,41 @@ PaintRowGroupBackgroundByColIdx(nsTableRowGroupFrame* aRowGroup,
|
||||
nsIFrame* aFrame,
|
||||
nsDisplayListBuilder* aBuilder,
|
||||
const nsDisplayListSet& aLists,
|
||||
const nsTArray<int32_t>& aColIdx,
|
||||
const nsTArray<uint32_t>& aColIdx,
|
||||
const nsPoint& aOffset)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(!aColIdx.IsEmpty(),
|
||||
"Must be painting backgrounds for something");
|
||||
|
||||
for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) {
|
||||
auto rowPos = row->GetNormalPosition() + aOffset;
|
||||
if (!aBuilder->GetDirtyRect().Intersects(nsRect(rowPos, row->GetSize()))) {
|
||||
continue;
|
||||
}
|
||||
for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) {
|
||||
uint32_t curColIdx = cell->ColIndex();
|
||||
if (!aColIdx.ContainsSorted(curColIdx)) {
|
||||
if (curColIdx > aColIdx.LastElement()) {
|
||||
// We can just stop looking at this row.
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cell->ShouldPaintBackground(aBuilder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t curColIdx;
|
||||
cell->GetColIndex(curColIdx);
|
||||
if (aColIdx.Contains(curColIdx)) {
|
||||
auto cellPos = cell->GetNormalPosition() + rowPos;
|
||||
auto cellRect = nsRect(cellPos, cell->GetSize());
|
||||
if (!aBuilder->GetDirtyRect().Intersects(cellRect)) {
|
||||
continue;
|
||||
}
|
||||
nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
|
||||
aLists.BorderBackground(),
|
||||
false, nullptr,
|
||||
aFrame->GetRectRelativeToSelf(),
|
||||
cell);
|
||||
auto cellPos = cell->GetNormalPosition() + rowPos;
|
||||
auto cellRect = nsRect(cellPos, cell->GetSize());
|
||||
if (!aBuilder->GetDirtyRect().Intersects(cellRect)) {
|
||||
continue;
|
||||
}
|
||||
nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
|
||||
aLists.BorderBackground(),
|
||||
false, nullptr,
|
||||
aFrame->GetRectRelativeToSelf(),
|
||||
cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1547,8 +1550,10 @@ nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
|
||||
// Compute background rect by iterating all cell frame.
|
||||
nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(aFrame);
|
||||
// Collecting column index.
|
||||
AutoTArray<int32_t, 1> colIdx;
|
||||
AutoTArray<uint32_t, 1> colIdx;
|
||||
for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; col = col->GetNextCol()) {
|
||||
MOZ_ASSERT(colIdx.IsEmpty() ||
|
||||
static_cast<uint32_t>(col->GetColIndex()) > colIdx.LastElement());
|
||||
colIdx.AppendElement(col->GetColIndex());
|
||||
}
|
||||
|
||||
@ -1565,7 +1570,7 @@ nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
|
||||
} else if (aFrame->IsTableColFrame()) {
|
||||
// Compute background rect by iterating all cell frame.
|
||||
nsTableColFrame* col = static_cast<nsTableColFrame*>(aFrame);
|
||||
AutoTArray<int32_t, 1> colIdx;
|
||||
AutoTArray<uint32_t, 1> colIdx;
|
||||
colIdx.AppendElement(col->GetColIndex());
|
||||
|
||||
nsTableFrame* table = col->GetTableFrame();
|
||||
@ -4225,9 +4230,8 @@ nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame)
|
||||
for (nsIFrame* childFrame : cFrame->PrincipalChildList()) {
|
||||
nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
|
||||
if (cellFrame) {
|
||||
int32_t colIndex;
|
||||
cellFrame->GetColIndex(colIndex);
|
||||
printf("cell(%d)=%p ", colIndex, static_cast<void*>(childFrame));
|
||||
uint32_t colIndex = cellFrame->ColIndex();
|
||||
printf("cell(%u)=%p ", colIndex, static_cast<void*>(childFrame));
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -260,7 +260,7 @@ nsTableRowFrame::InsertFrames(ChildListID aListID,
|
||||
// insert the cells into the cell map
|
||||
int32_t colIndex = -1;
|
||||
if (prevCellFrame) {
|
||||
prevCellFrame->GetColIndex(colIndex);
|
||||
colIndex = prevCellFrame->ColIndex();
|
||||
}
|
||||
tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex);
|
||||
|
||||
@ -329,18 +329,6 @@ GetBSizeOfRowsSpannedBelowFirst(nsTableCellFrame& aTableCellFrame,
|
||||
return bsize;
|
||||
}
|
||||
|
||||
nsTableCellFrame*
|
||||
nsTableRowFrame::GetFirstCell()
|
||||
{
|
||||
for (nsIFrame* childFrame : mFrames) {
|
||||
nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
|
||||
if (cellFrame) {
|
||||
return cellFrame;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-reflow hook. This is where the table row does its post-processing
|
||||
*/
|
||||
@ -687,8 +675,7 @@ CalcAvailISize(nsTableFrame& aTableFrame,
|
||||
nsTableCellFrame& aCellFrame)
|
||||
{
|
||||
nscoord cellAvailISize = 0;
|
||||
int32_t colIndex;
|
||||
aCellFrame.GetColIndex(colIndex);
|
||||
uint32_t colIndex = aCellFrame.ColIndex();
|
||||
int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame);
|
||||
NS_ASSERTION(colspan > 0, "effective colspan should be positive");
|
||||
nsTableFrame* fifTable =
|
||||
@ -827,12 +814,12 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext,
|
||||
}
|
||||
}
|
||||
|
||||
int32_t cellColIndex;
|
||||
cellFrame->GetColIndex(cellColIndex);
|
||||
uint32_t cellColIndex = cellFrame->ColIndex();
|
||||
cellColSpan = aTableFrame.GetEffectiveColSpan(*cellFrame);
|
||||
|
||||
// If the adjacent cell is in a prior row (because of a rowspan) add in the space
|
||||
if (prevColIndex != (cellColIndex - 1)) {
|
||||
// NOTE: prevColIndex can be -1 here.
|
||||
if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) {
|
||||
iCoord += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, aTableFrame,
|
||||
false);
|
||||
}
|
||||
@ -1207,8 +1194,7 @@ nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset,
|
||||
shift = rowRect.BSize(wm);
|
||||
nsTableCellFrame* cellFrame = GetFirstCell();
|
||||
if (cellFrame) {
|
||||
int32_t rowIndex;
|
||||
cellFrame->GetRowIndex(rowIndex);
|
||||
uint32_t rowIndex = cellFrame->RowIndex();
|
||||
shift += tableFrame->GetRowSpacing(rowIndex);
|
||||
while (cellFrame) {
|
||||
LogicalRect cRect = cellFrame->GetLogicalRect(wm, containerSize);
|
||||
@ -1239,13 +1225,13 @@ nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset,
|
||||
for (nsIFrame* kidFrame : mFrames) {
|
||||
nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
|
||||
if (cellFrame) {
|
||||
int32_t cellColIndex;
|
||||
cellFrame->GetColIndex(cellColIndex);
|
||||
uint32_t cellColIndex = cellFrame->ColIndex();
|
||||
int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame);
|
||||
|
||||
// If the adjacent cell is in a prior row (because of a rowspan) add in
|
||||
// the space
|
||||
if (prevColIndex != (cellColIndex - 1)) {
|
||||
// NOTE: prevColIndex can be -1 here.
|
||||
if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) {
|
||||
iPos += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan,
|
||||
*tableFrame, true);
|
||||
}
|
||||
@ -1358,9 +1344,9 @@ nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame,
|
||||
for (nsIFrame* child : mFrames) {
|
||||
nsTableCellFrame *cellFrame = do_QueryFrame(child);
|
||||
if (cellFrame) {
|
||||
int32_t colIndex;
|
||||
cellFrame->GetColIndex(colIndex);
|
||||
if (colIndex < aColIndex) {
|
||||
uint32_t colIndex = cellFrame->ColIndex();
|
||||
// Can aColIndex be -1 here? Let's assume it can for now.
|
||||
if (static_cast<int32_t>(colIndex) < aColIndex) {
|
||||
priorCell = cellFrame;
|
||||
}
|
||||
else break;
|
||||
|
@ -80,7 +80,9 @@ public:
|
||||
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsDisplayListSet& aLists) override;
|
||||
|
||||
nsTableCellFrame* GetFirstCell() ;
|
||||
// Implemented in nsTableCellFrame.h, because it needs to know about the
|
||||
// nsTableCellFrame class, but we can't include nsTableCellFrame.h here.
|
||||
inline nsTableCellFrame* GetFirstCell() const;
|
||||
|
||||
/** calls Reflow for all of its child cells.
|
||||
* Cells with rowspan=1 are all set to the same height and stacked horizontally.
|
||||
|
@ -181,8 +181,7 @@ nsTableRowGroupFrame::InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame
|
||||
while (copyCellFrame && originalCellFrame) {
|
||||
NS_ASSERTION(originalCellFrame->GetContent() == copyCellFrame->GetContent(),
|
||||
"cell frames have different content");
|
||||
int32_t colIndex;
|
||||
originalCellFrame->GetColIndex(colIndex);
|
||||
uint32_t colIndex = originalCellFrame->ColIndex();
|
||||
copyCellFrame->SetColIndex(colIndex);
|
||||
|
||||
// Move to the next cell frame
|
||||
@ -1043,8 +1042,7 @@ nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext,
|
||||
nsTableCellFrame* contCell = static_cast<nsTableCellFrame*>(
|
||||
aPresContext.PresShell()->FrameConstructor()->
|
||||
CreateContinuingFrame(&aPresContext, cell, &aLastRow));
|
||||
int32_t colIndex;
|
||||
cell->GetColIndex(colIndex);
|
||||
uint32_t colIndex = cell->ColIndex();
|
||||
aContRow->InsertCellFrame(contCell, colIndex);
|
||||
}
|
||||
}
|
||||
|
@ -330,9 +330,17 @@ class ReftestArgumentsParser(argparse.ArgumentParser):
|
||||
|
||||
options.leakThresholds = {
|
||||
"default": options.defaultLeakThreshold,
|
||||
"tab": 5000, # See dependencies of bug 1051230.
|
||||
"tab": options.defaultLeakThreshold,
|
||||
}
|
||||
|
||||
if mozinfo.isWin:
|
||||
if mozinfo.info['bits'] == 32:
|
||||
# See bug 1408554.
|
||||
options.leakThresholds["tab"] = 3000
|
||||
else:
|
||||
# See bug 1404482.
|
||||
options.leakThresholds["tab"] = 100
|
||||
|
||||
|
||||
class DesktopArgumentsParser(ReftestArgumentsParser):
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -28,10 +28,15 @@
|
||||
#include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
|
||||
#include "webrtc/common_video/include/video_frame_buffer.h"
|
||||
#include "webrtc/api/video/i420_buffer.h"
|
||||
|
||||
#ifdef WEBRTC_MAC
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_X_VERSION_10_8) && \
|
||||
(MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8)
|
||||
// XXX not available in Mac 10.7 SDK
|
||||
#include "webrtc/sdk/objc/Framework/Classes/corevideo_frame_buffer.h"
|
||||
#include "webrtc/common_video/include/corevideo_frame_buffer.h"
|
||||
#endif
|
||||
|
||||
#include "mozilla/Unused.h"
|
||||
@ -1999,10 +2004,10 @@ WebrtcVideoConduit::SendVideoFrame(webrtc::VideoFrame& frame)
|
||||
(MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8)
|
||||
// XXX not available in Mac 10.7 SDK
|
||||
// code adapted from objvideotracksource.mm
|
||||
} else if (frame.nativeHandle) {
|
||||
} else if (frame.video_frame_buffer()->native_handle()) {
|
||||
// Adapted CVPixelBuffer frame.
|
||||
buffer = new rtc::RefCountedObject<CoreVideoFrameBuffer>(
|
||||
static_cast<CVPixelBufferRef>(frame.nativeHandle), adapted_width, adapted_height,
|
||||
buffer = new rtc::RefCountedObject<webrtc::CoreVideoFrameBuffer>(
|
||||
static_cast<CVPixelBufferRef>(frame.video_frame_buffer()->native_handle()), adapted_width, adapted_height,
|
||||
crop_width, crop_height, crop_x, crop_y);
|
||||
#endif
|
||||
#elif WEBRTC_WIN
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
interface nsIChannel;
|
||||
interface nsIConsoleReportCollector;
|
||||
interface nsIInputStream;
|
||||
interface nsIOutputStream;
|
||||
interface nsIURI;
|
||||
|
||||
@ -26,6 +27,16 @@ native TimeStamp(mozilla::TimeStamp);
|
||||
|
||||
[ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
|
||||
|
||||
/**
|
||||
* Interface allowing the nsIInterceptedChannel to callback when it is
|
||||
* done reading from the body stream.
|
||||
*/
|
||||
[scriptable, uuid(51039eb6-bea0-40c7-b523-ccab56cc4fde)]
|
||||
interface nsIInterceptedBodyCallback : nsISupports
|
||||
{
|
||||
void bodyComplete(in nsresult aRv);
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface to allow implementors of nsINetworkInterceptController to control the behaviour
|
||||
* of intercepted channels without tying implementation details of the interception to
|
||||
@ -55,13 +66,28 @@ interface nsIInterceptedChannel : nsISupports
|
||||
void synthesizeHeader(in ACString name, in ACString value);
|
||||
|
||||
/**
|
||||
* Instruct a channel that has been intercepted that a response has been
|
||||
* synthesized and can now be read. No further header modification is allowed
|
||||
* after this point. The caller may optionally pass a spec for a URL that
|
||||
* this response originates from; an empty string will cause the original
|
||||
* intercepted request's URL to be used instead.
|
||||
* Instruct a channel that has been intercepted that a response is
|
||||
* starting to be synthesized. No further header modification is allowed
|
||||
* after this point. There are a few parameters:
|
||||
* - A body stream may be optionally passed. If nullptr, then an
|
||||
* empty body is assumed.
|
||||
* - A callback may be optionally passed. It will be invoked
|
||||
* when the body is complete. For a nullptr body this may be
|
||||
* synchronously on the current thread. Otherwise it will be invoked
|
||||
* asynchronously on the current thread.
|
||||
* - The caller may optionally pass a spec for a URL that this response
|
||||
* originates from; an empty string will cause the original
|
||||
* intercepted request's URL to be used instead.
|
||||
*/
|
||||
void finishSynthesizedResponse(in ACString finalURLSpec);
|
||||
void startSynthesizedResponse(in nsIInputStream body,
|
||||
in nsIInterceptedBodyCallback callback,
|
||||
in ACString finalURLSpec);
|
||||
|
||||
/**
|
||||
* Instruct a channel that has been intercepted that response synthesis
|
||||
* has completed and all outstanding resources can be closed.
|
||||
*/
|
||||
void finishSynthesizedResponse();
|
||||
|
||||
/**
|
||||
* Cancel the pending intercepted request.
|
||||
@ -70,11 +96,6 @@ interface nsIInterceptedChannel : nsISupports
|
||||
*/
|
||||
void cancelInterception(in nsresult status);
|
||||
|
||||
/**
|
||||
* The synthesized response body to be produced.
|
||||
*/
|
||||
readonly attribute nsIOutputStream responseBody;
|
||||
|
||||
/**
|
||||
* The underlying channel object that was intercepted.
|
||||
*/
|
||||
|
@ -81,7 +81,6 @@ NS_IMETHODIMP
|
||||
InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
||||
{
|
||||
if (mOwner) {
|
||||
mOwner->SynthesizeResponseStartTime(TimeStamp::Now());
|
||||
mOwner->DoOnStartRequest(mOwner, mContext);
|
||||
}
|
||||
return NS_OK;
|
||||
@ -140,7 +139,6 @@ NS_IMETHODIMP
|
||||
InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
|
||||
{
|
||||
if (mOwner) {
|
||||
mOwner->SynthesizeResponseEndTime(TimeStamp::Now());
|
||||
mOwner->DoPreOnStopRequest(aStatusCode);
|
||||
mOwner->DoOnStopRequest(mOwner, aStatusCode, mContext);
|
||||
}
|
||||
@ -1126,6 +1124,8 @@ HttpChannelChild::DoPreOnStopRequest(nsresult aStatus)
|
||||
this, static_cast<uint32_t>(aStatus)));
|
||||
mIsPending = false;
|
||||
|
||||
MaybeCallSynthesizedCallback();
|
||||
|
||||
Performance* documentPerformance = GetPerformance();
|
||||
if (documentPerformance) {
|
||||
documentPerformance->AddEntry(this, this);
|
||||
@ -1410,6 +1410,8 @@ HttpChannelChild::DoNotifyListenerCleanup()
|
||||
mInterceptListener->Cleanup();
|
||||
mInterceptListener = nullptr;
|
||||
}
|
||||
|
||||
MaybeCallSynthesizedCallback();
|
||||
}
|
||||
|
||||
class DeleteSelfEvent : public NeckoTargetChannelEvent<HttpChannelChild>
|
||||
@ -1433,6 +1435,7 @@ HttpChannelChild::OverrideRunnable::OverrideRunnable(
|
||||
HttpChannelChild* aNewChannel,
|
||||
InterceptStreamListener* aListener,
|
||||
nsIInputStream* aInput,
|
||||
nsIInterceptedBodyCallback* aCallback,
|
||||
nsAutoPtr<nsHttpResponseHead>& aHead)
|
||||
: Runnable("net::HttpChannelChild::OverrideRunnable")
|
||||
{
|
||||
@ -1440,6 +1443,7 @@ HttpChannelChild::OverrideRunnable::OverrideRunnable(
|
||||
mNewChannel = aNewChannel;
|
||||
mListener = aListener;
|
||||
mInput = aInput;
|
||||
mCallback = aCallback;
|
||||
mHead = aHead;
|
||||
}
|
||||
|
||||
@ -1447,13 +1451,26 @@ void
|
||||
HttpChannelChild::OverrideRunnable::OverrideWithSynthesizedResponse()
|
||||
{
|
||||
if (mNewChannel) {
|
||||
mNewChannel->OverrideWithSynthesizedResponse(mHead, mInput, mListener);
|
||||
mNewChannel->OverrideWithSynthesizedResponse(mHead, mInput, mCallback, mListener);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::OverrideRunnable::Run()
|
||||
{
|
||||
// Check to see if the channel was canceled in the middle of the redirect.
|
||||
nsresult rv = NS_OK;
|
||||
Unused << mChannel->GetStatus(&rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
if (mCallback) {
|
||||
mCallback->BodyComplete(rv);
|
||||
mCallback = nullptr;
|
||||
}
|
||||
mChannel->CleanupRedirectingChannel(rv);
|
||||
mNewChannel->Cancel(rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool ret = mChannel->Redirect3Complete(this);
|
||||
|
||||
// If the method returns false, it means the IPDL connection is being
|
||||
@ -2138,9 +2155,12 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
|
||||
nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
|
||||
MOZ_ASSERT(neckoTarget);
|
||||
|
||||
nsCOMPtr<nsIInterceptedBodyCallback> callback =
|
||||
mSynthesizedCallback.forget();
|
||||
|
||||
Unused << neckoTarget->Dispatch(
|
||||
new OverrideRunnable(this, redirectedChannel, streamListener,
|
||||
mSynthesizedInput, mResponseHead),
|
||||
mSynthesizedInput, callback, mResponseHead),
|
||||
NS_DISPATCH_NORMAL);
|
||||
|
||||
return NS_OK;
|
||||
@ -2192,6 +2212,8 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
|
||||
}
|
||||
}
|
||||
|
||||
MaybeCallSynthesizedCallback();
|
||||
|
||||
bool chooseAppcache = false;
|
||||
nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
|
||||
do_QueryInterface(newHttpChannel);
|
||||
@ -2222,12 +2244,23 @@ HttpChannelChild::Cancel(nsresult status)
|
||||
// is responsible for cleaning up.
|
||||
mCanceled = true;
|
||||
mStatus = status;
|
||||
if (RemoteChannelExists())
|
||||
if (RemoteChannelExists()) {
|
||||
SendCancel(status);
|
||||
}
|
||||
|
||||
// If the channel is intercepted and already pumping, then just
|
||||
// cancel the pump. This will call OnStopRequest().
|
||||
if (mSynthesizedResponsePump) {
|
||||
mSynthesizedResponsePump->Cancel(status);
|
||||
}
|
||||
mInterceptListener = nullptr;
|
||||
|
||||
// If we are canceled while intercepting, but not yet pumping, then
|
||||
// we must call AsyncAbort() to trigger OnStopRequest().
|
||||
else if (mInterceptListener) {
|
||||
mInterceptListener->Cleanup();
|
||||
mInterceptListener = nullptr;
|
||||
Unused << AsyncAbort(status);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
@ -2391,7 +2424,6 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
|
||||
// We may have been canceled already, either by on-modify-request
|
||||
// listeners or by load group observers; in that case, don't create IPDL
|
||||
// connection. See nsHttpChannel::AsyncOpen().
|
||||
Unused << AsyncAbort(mStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -3416,10 +3448,15 @@ HttpChannelChild::ResetInterception()
|
||||
mLoadFlags |= LOAD_BYPASS_SERVICE_WORKER;
|
||||
}
|
||||
|
||||
// If the channel has already been aborted or canceled, just stop.
|
||||
if (NS_FAILED(mStatus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue with the original cross-process request
|
||||
nsresult rv = ContinueAsyncOpen();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
Unused << AsyncAbort(rv);
|
||||
Unused << Cancel(rv);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3536,8 +3573,28 @@ HttpChannelChild::CancelOnMainThread(nsresult aRv)
|
||||
void
|
||||
HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
|
||||
nsIInputStream* aSynthesizedInput,
|
||||
nsIInterceptedBodyCallback* aSynthesizedCallback,
|
||||
InterceptStreamListener* aStreamListener)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
auto autoCleanup = MakeScopeExit([&] {
|
||||
// Auto-cancel on failure. Do this first to get mStatus set, if necessary.
|
||||
if (NS_FAILED(rv)) {
|
||||
Cancel(rv);
|
||||
}
|
||||
|
||||
// If we early exit before taking ownership of the body, then automatically
|
||||
// invoke the callback. This could be due to an error or because we're not
|
||||
// going to consume it due to a redirect, etc.
|
||||
if (aSynthesizedCallback) {
|
||||
aSynthesizedCallback->BodyComplete(mStatus);
|
||||
}
|
||||
});
|
||||
|
||||
if (NS_FAILED(mStatus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInterceptListener = aStreamListener;
|
||||
|
||||
// Intercepted responses should already be decoded. If its a redirect,
|
||||
@ -3549,41 +3606,49 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>&
|
||||
mResponseHead = aResponseHead;
|
||||
mSynthesizedResponse = true;
|
||||
|
||||
mSynthesizedInput = aSynthesizedInput;
|
||||
|
||||
if (!mSynthesizedInput) {
|
||||
rv = NS_NewCStringInputStream(getter_AddRefs(mSynthesizedInput),
|
||||
EmptyCString());
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
}
|
||||
|
||||
if (nsHttpChannel::WillRedirect(mResponseHead)) {
|
||||
mShouldInterceptSubsequentRedirect = true;
|
||||
// Continue with the original cross-process request
|
||||
nsresult rv = ContinueAsyncOpen();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
rv = AsyncAbort(rv);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
rv = ContinueAsyncOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
// In our current implementation, the FetchEvent handler will copy the
|
||||
// response stream completely into the pipe backing the input stream so we
|
||||
// can treat the available as the length of the stream.
|
||||
uint64_t available;
|
||||
nsresult rv = aSynthesizedInput->Available(&available);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
// For progress we trust the content-length for the "maximum" size.
|
||||
// We can't determine the full size from the stream itself since we
|
||||
// only receive the data incrementally. We can't trust Available()
|
||||
// here.
|
||||
// TODO: We could implement an nsIFixedLengthInputStream interface and
|
||||
// QI to it here. This would let us determine the total length
|
||||
// for streams that support it. See bug 1388774.
|
||||
rv = GetContentLength(&mSynthesizedStreamLength);
|
||||
if (NS_FAILED(rv)) {
|
||||
mSynthesizedStreamLength = -1;
|
||||
} else {
|
||||
mSynthesizedStreamLength = int64_t(available);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
|
||||
MOZ_ASSERT(neckoTarget);
|
||||
|
||||
rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
|
||||
aSynthesizedInput, 0, 0, true, neckoTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aSynthesizedInput->Close();
|
||||
return;
|
||||
}
|
||||
mSynthesizedInput, 0, 0, true, neckoTarget);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
rv = mSynthesizedResponsePump->AsyncRead(aStreamListener, nullptr);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
// The pump is started, so take ownership of the body callback. We
|
||||
// clear the argument to avoid auto-completing it via the ScopeExit
|
||||
// lambda.
|
||||
mSynthesizedCallback = aSynthesizedCallback;
|
||||
aSynthesizedCallback = nullptr;
|
||||
|
||||
// if this channel has been suspended previously, the pump needs to be
|
||||
// correspondingly suspended now that it exists.
|
||||
for (uint32_t i = 0; i < mSuspendCount; i++) {
|
||||
@ -3591,9 +3656,7 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>&
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
}
|
||||
|
||||
if (mCanceled) {
|
||||
mSynthesizedResponsePump->Cancel(mStatus);
|
||||
}
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -3607,9 +3670,11 @@ HttpChannelChild::ForceIntercepted(bool aPostRedirectChannelShouldIntercept,
|
||||
}
|
||||
|
||||
void
|
||||
HttpChannelChild::ForceIntercepted(nsIInputStream* aSynthesizedInput)
|
||||
HttpChannelChild::ForceIntercepted(nsIInputStream* aSynthesizedInput,
|
||||
nsIInterceptedBodyCallback* aSynthesizedCallback)
|
||||
{
|
||||
mSynthesizedInput = aSynthesizedInput;
|
||||
mSynthesizedCallback = aSynthesizedCallback;
|
||||
mSynthesizedResponse = true;
|
||||
mRedirectingForSubsequentSynthesizedResponse = true;
|
||||
}
|
||||
@ -3703,15 +3768,14 @@ HttpChannelChild::LogBlockedCORSRequest(const nsAString & aMessage)
|
||||
}
|
||||
|
||||
void
|
||||
HttpChannelChild::SynthesizeResponseStartTime(const TimeStamp& aTime)
|
||||
HttpChannelChild::MaybeCallSynthesizedCallback()
|
||||
{
|
||||
mTransactionTimings.responseStart = aTime;
|
||||
}
|
||||
if (!mSynthesizedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
HttpChannelChild::SynthesizeResponseEndTime(const TimeStamp& aTime)
|
||||
{
|
||||
mTransactionTimings.responseEnd = aTime;
|
||||
mSynthesizedCallback->BodyComplete(mStatus);
|
||||
mSynthesizedCallback = nullptr;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
|
@ -39,6 +39,7 @@ using mozilla::Telemetry::LABELS_HTTP_CHILD_OMT_STATS;
|
||||
|
||||
class nsIEventTarget;
|
||||
class nsInputStreamPump;
|
||||
class nsIInterceptedBodyCallback;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
@ -205,6 +206,7 @@ private:
|
||||
HttpChannelChild* aNewChannel,
|
||||
InterceptStreamListener* aListener,
|
||||
nsIInputStream* aInput,
|
||||
nsIInterceptedBodyCallback* aCallback,
|
||||
nsAutoPtr<nsHttpResponseHead>& aHead);
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
@ -214,6 +216,7 @@ private:
|
||||
RefPtr<HttpChannelChild> mNewChannel;
|
||||
RefPtr<InterceptStreamListener> mListener;
|
||||
nsCOMPtr<nsIInputStream> mInput;
|
||||
nsCOMPtr<nsIInterceptedBodyCallback> mCallback;
|
||||
nsAutoPtr<nsHttpResponseHead> mHead;
|
||||
};
|
||||
|
||||
@ -265,9 +268,11 @@ private:
|
||||
// asynchronously read from the pump.
|
||||
void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
|
||||
nsIInputStream* aSynthesizedInput,
|
||||
nsIInterceptedBodyCallback* aSynthesizedCallback,
|
||||
InterceptStreamListener* aStreamListener);
|
||||
|
||||
void ForceIntercepted(nsIInputStream* aSynthesizedInput);
|
||||
void ForceIntercepted(nsIInputStream* aSynthesizedInput,
|
||||
nsIInterceptedBodyCallback* aSynthesizedCallback);
|
||||
|
||||
// Try send DeletingChannel message to parent side. Dispatch an async task to
|
||||
// main thread if invoking on non-main thread.
|
||||
@ -278,14 +283,12 @@ private:
|
||||
void CancelOnMainThread(nsresult aRv);
|
||||
|
||||
void
|
||||
SynthesizeResponseStartTime(const TimeStamp& aTime);
|
||||
|
||||
void
|
||||
SynthesizeResponseEndTime(const TimeStamp& aTime);
|
||||
MaybeCallSynthesizedCallback();
|
||||
|
||||
RequestHeaderTuples mClientSetRequestHeaders;
|
||||
RefPtr<nsInputStreamPump> mSynthesizedResponsePump;
|
||||
nsCOMPtr<nsIInputStream> mSynthesizedInput;
|
||||
nsCOMPtr<nsIInterceptedBodyCallback> mSynthesizedCallback;
|
||||
int64_t mSynthesizedStreamLength;
|
||||
|
||||
bool mIsFromCache;
|
||||
|
@ -318,7 +318,8 @@ public:
|
||||
{
|
||||
// The URL passed as an argument here doesn't matter, since the child will
|
||||
// receive a redirection notification as a result of this synthesized response.
|
||||
mChannel->FinishSynthesizedResponse(EmptyCString());
|
||||
mChannel->StartSynthesizedResponse(nullptr, nullptr, EmptyCString());
|
||||
mChannel->FinishSynthesizedResponse();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "HttpLog.h"
|
||||
|
||||
#include "InterceptedChannel.h"
|
||||
#include "nsICancelable.h"
|
||||
#include "nsInputStreamPump.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsIStreamListener.h"
|
||||
@ -18,6 +19,7 @@
|
||||
#include "mozilla/ConsoleReportCollector.h"
|
||||
#include "mozilla/dom/ChannelInfo.h"
|
||||
#include "nsIChannelEventSink.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
@ -48,13 +50,6 @@ InterceptedChannelBase::~InterceptedChannelBase()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedChannelBase::GetResponseBody(nsIOutputStream** aStream)
|
||||
{
|
||||
NS_IF_ADDREF(*aStream = mResponseBody);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
InterceptedChannelBase::EnsureSynthesizedResponse()
|
||||
{
|
||||
@ -227,11 +222,6 @@ InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
|
||||
void
|
||||
InterceptedChannelContent::NotifyController()
|
||||
{
|
||||
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
|
||||
getter_AddRefs(mResponseBody),
|
||||
0, UINT32_MAX, true, true);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
DoNotifyController();
|
||||
}
|
||||
|
||||
@ -251,10 +241,6 @@ InterceptedChannelContent::ResetInterception()
|
||||
|
||||
mReportCollector->FlushConsoleReports(mChannel);
|
||||
|
||||
mResponseBody->Close();
|
||||
mResponseBody = nullptr;
|
||||
mSynthesizedInput = nullptr;
|
||||
|
||||
mChannel->ResetInterception();
|
||||
|
||||
mClosed = true;
|
||||
@ -265,7 +251,7 @@ InterceptedChannelContent::ResetInterception()
|
||||
NS_IMETHODIMP
|
||||
InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
|
||||
{
|
||||
if (!mResponseBody) {
|
||||
if (mClosed) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
@ -275,7 +261,7 @@ InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus, const nsACString&
|
||||
NS_IMETHODIMP
|
||||
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
|
||||
{
|
||||
if (!mResponseBody) {
|
||||
if (mClosed) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
@ -283,19 +269,14 @@ InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACS
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
|
||||
InterceptedChannelContent::StartSynthesizedResponse(nsIInputStream* aBody,
|
||||
nsIInterceptedBodyCallback* aBodyCallback,
|
||||
const nsACString& aFinalURLSpec)
|
||||
{
|
||||
if (NS_WARN_IF(mClosed)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
// Make sure the body output stream is always closed. If the channel was
|
||||
// intercepted with a null-body response then its possible the synthesis
|
||||
// completed without a stream copy operation.
|
||||
mResponseBody->Close();
|
||||
|
||||
mReportCollector->FlushConsoleReports(mChannel);
|
||||
|
||||
EnsureSynthesizedResponse();
|
||||
|
||||
nsCOMPtr<nsIURI> originalURI;
|
||||
@ -316,15 +297,26 @@ InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURL
|
||||
bool equal = false;
|
||||
originalURI->Equals(responseURI, &equal);
|
||||
if (!equal) {
|
||||
mChannel->ForceIntercepted(mSynthesizedInput);
|
||||
mChannel->ForceIntercepted(aBody, aBodyCallback);
|
||||
mChannel->BeginNonIPCRedirect(responseURI, *mSynthesizedResponseHead.ptr());
|
||||
} else {
|
||||
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(),
|
||||
mSynthesizedInput,
|
||||
aBody, aBodyCallback,
|
||||
mStreamListener);
|
||||
}
|
||||
|
||||
mResponseBody = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedChannelContent::FinishSynthesizedResponse()
|
||||
{
|
||||
if (NS_WARN_IF(mClosed)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mReportCollector->FlushConsoleReports(mChannel);
|
||||
|
||||
mStreamListener = nullptr;
|
||||
mClosed = true;
|
||||
|
||||
@ -339,15 +331,12 @@ InterceptedChannelContent::CancelInterception(nsresult aStatus)
|
||||
if (mClosed) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mClosed = true;
|
||||
|
||||
mReportCollector->FlushConsoleReports(mChannel);
|
||||
|
||||
// we need to use AsyncAbort instead of Cancel since there's no active pump
|
||||
// to cancel which will provide OnStart/OnStopRequest to the channel.
|
||||
nsresult rv = mChannel->AsyncAbort(aStatus);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
Unused << mChannel->Cancel(aStatus);
|
||||
mStreamListener = nullptr;
|
||||
mClosed = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -30,9 +30,6 @@ protected:
|
||||
// The interception controller to notify about the successful channel interception
|
||||
nsCOMPtr<nsINetworkInterceptController> mController;
|
||||
|
||||
// The stream to write the body of the synthesized response
|
||||
nsCOMPtr<nsIOutputStream> mResponseBody;
|
||||
|
||||
// Response head for use when synthesizing
|
||||
Maybe<nsAutoPtr<nsHttpResponseHead>> mSynthesizedResponseHead;
|
||||
|
||||
@ -73,7 +70,6 @@ public:
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD GetResponseBody(nsIOutputStream** aOutput) override;
|
||||
NS_IMETHOD GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut) override;
|
||||
NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override;
|
||||
|
||||
@ -171,9 +167,6 @@ class InterceptedChannelContent : public InterceptedChannelBase
|
||||
// The actual channel being intercepted.
|
||||
RefPtr<HttpChannelChild> mChannel;
|
||||
|
||||
// Reader-side of the response body when synthesizing in a child proces
|
||||
nsCOMPtr<nsIInputStream> mSynthesizedInput;
|
||||
|
||||
// Listener for the synthesized response to fix up the notifications before they reach
|
||||
// the actual channel.
|
||||
RefPtr<InterceptStreamListener> mStreamListener;
|
||||
@ -187,7 +180,10 @@ public:
|
||||
bool aSecureUpgrade);
|
||||
|
||||
NS_IMETHOD ResetInterception() override;
|
||||
NS_IMETHOD FinishSynthesizedResponse(const nsACString& aFinalURLSpec) override;
|
||||
NS_IMETHOD StartSynthesizedResponse(nsIInputStream* aBody,
|
||||
nsIInterceptedBodyCallback* aBodyCallback,
|
||||
const nsACString& aFinalURLSpec) override;
|
||||
NS_IMETHOD FinishSynthesizedResponse() override;
|
||||
NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
|
||||
NS_IMETHOD GetSecureUpgradedChannelURI(nsIURI** aURI) override;
|
||||
NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
|
||||
|
@ -50,9 +50,9 @@ InterceptedHttpChannel::ReleaseListeners()
|
||||
mSynthesizedResponseHead.reset();
|
||||
mRedirectChannel = nullptr;
|
||||
mBodyReader = nullptr;
|
||||
mBodyWriter = nullptr;
|
||||
mReleaseHandle = nullptr;
|
||||
mProgressSink = nullptr;
|
||||
mBodyCallback = nullptr;
|
||||
mPump = nullptr;
|
||||
mParentChannel = nullptr;
|
||||
|
||||
@ -240,8 +240,13 @@ InterceptedHttpChannel::RedirectForOpaqueResponse(nsIURI* aResponseURI)
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// We want to pass ownership of the body callback to the new synthesized
|
||||
// channel. We need to hold a reference to the callbacks on the stack
|
||||
// as well, though, so we can call them if a failure occurs.
|
||||
nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = mBodyCallback.forget();
|
||||
|
||||
RefPtr<InterceptedHttpChannel> newChannel =
|
||||
CreateForSynthesis(mResponseHead, mBodyReader,
|
||||
CreateForSynthesis(mResponseHead, mBodyReader, bodyCallback,
|
||||
mChannelCreationTime, mChannelCreationTimestamp,
|
||||
mAsyncOpenTime);
|
||||
|
||||
@ -264,6 +269,11 @@ InterceptedHttpChannel::RedirectForOpaqueResponse(nsIURI* aResponseURI)
|
||||
rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
// Make sure to call the body callback since we took ownership
|
||||
// above. Neither the new channel or our standard
|
||||
// OnRedirectVerifyCallback() code will invoke the callback. Do it here.
|
||||
bodyCallback->BodyComplete(rv);
|
||||
|
||||
OnRedirectVerifyCallback(rv);
|
||||
}
|
||||
|
||||
@ -312,6 +322,8 @@ InterceptedHttpChannel::StartPump()
|
||||
mPump->Suspend();
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -413,6 +425,15 @@ InterceptedHttpChannel::MaybeCallStatusAndProgress()
|
||||
mProgressReported = progress;
|
||||
}
|
||||
|
||||
void
|
||||
InterceptedHttpChannel::MaybeCallBodyCallback()
|
||||
{
|
||||
nsCOMPtr<nsIInterceptedBodyCallback> callback = mBodyCallback.forget();
|
||||
if (callback) {
|
||||
callback->BodyComplete(mStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<InterceptedHttpChannel>
|
||||
InterceptedHttpChannel::CreateForInterception(PRTime aCreationTime,
|
||||
@ -432,6 +453,7 @@ InterceptedHttpChannel::CreateForInterception(PRTime aCreationTime,
|
||||
already_AddRefed<InterceptedHttpChannel>
|
||||
InterceptedHttpChannel::CreateForSynthesis(const nsHttpResponseHead* aHead,
|
||||
nsIInputStream* aBody,
|
||||
nsIInterceptedBodyCallback* aBodyCallback,
|
||||
PRTime aCreationTime,
|
||||
const TimeStamp& aCreationTimestamp,
|
||||
const TimeStamp& aAsyncOpenTimestamp)
|
||||
@ -446,8 +468,9 @@ InterceptedHttpChannel::CreateForSynthesis(const nsHttpResponseHead* aHead,
|
||||
new InterceptedHttpChannel(aCreationTime, aCreationTimestamp,
|
||||
aAsyncOpenTimestamp);
|
||||
|
||||
ref->mBodyReader = aBody;
|
||||
ref->mResponseHead = new nsHttpResponseHead(*aHead);
|
||||
ref->mBodyReader = aBody;
|
||||
ref->mBodyCallback = aBodyCallback;
|
||||
|
||||
return ref.forget();
|
||||
}
|
||||
@ -693,15 +716,38 @@ InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName,
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
|
||||
InterceptedHttpChannel::StartSynthesizedResponse(nsIInputStream* aBody,
|
||||
nsIInterceptedBodyCallback* aBodyCallback,
|
||||
const nsACString& aFinalURLSpec)
|
||||
{
|
||||
if (mCanceled) {
|
||||
return mStatus;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
auto autoCleanup = MakeScopeExit([&] {
|
||||
// Auto-cancel on failure. Do this first to get mStatus set, if necessary.
|
||||
if (NS_FAILED(rv)) {
|
||||
Cancel(rv);
|
||||
}
|
||||
|
||||
// If we early exit before taking ownership of the body, then automatically
|
||||
// invoke the callback. This could be due to an error or because we're not
|
||||
// going to consume it due to a redirect, etc.
|
||||
if (aBodyCallback) {
|
||||
aBodyCallback->BodyComplete(mStatus);
|
||||
}
|
||||
});
|
||||
|
||||
if (NS_FAILED(mStatus)) {
|
||||
// Return NS_OK. The channel should fire callbacks with an error code
|
||||
// if it was cancelled before this point.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mBodyWriter) {
|
||||
mBodyWriter->Close();
|
||||
}
|
||||
// Take ownership of the body callbacks If a failure occurs we will
|
||||
// automatically Cancel() the channel. This will then invoke OnStopRequest()
|
||||
// which will invoke the correct callback. In the case of an opaque response
|
||||
// redirect we pass ownership of the callback to the new channel.
|
||||
mBodyCallback = aBodyCallback;
|
||||
aBodyCallback = nullptr;
|
||||
|
||||
if (!mSynthesizedResponseHead) {
|
||||
mSynthesizedResponseHead.reset(new nsHttpResponseHead());
|
||||
@ -710,7 +756,10 @@ InterceptedHttpChannel::FinishSynthesizedResponse(const nsACString& aFinalURLSpe
|
||||
mResponseHead = mSynthesizedResponseHead.release();
|
||||
|
||||
if (ShouldRedirect()) {
|
||||
return FollowSyntheticRedirect();
|
||||
rv = FollowSyntheticRedirect();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Intercepted responses should already be decoded.
|
||||
@ -718,15 +767,15 @@ InterceptedHttpChannel::FinishSynthesizedResponse(const nsACString& aFinalURLSpe
|
||||
|
||||
// Errors and redirects may not have a body. Synthesize an empty string stream
|
||||
// here so later code can be simpler.
|
||||
mBodyReader = aBody;
|
||||
if (!mBodyReader) {
|
||||
nsresult rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader),
|
||||
EmptyCString());
|
||||
rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), EmptyCString());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> responseURI;
|
||||
if (!aFinalURLSpec.IsEmpty()) {
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
|
||||
rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
responseURI = mURI;
|
||||
@ -735,10 +784,31 @@ InterceptedHttpChannel::FinishSynthesizedResponse(const nsACString& aFinalURLSpe
|
||||
bool equal = false;
|
||||
Unused << mURI->Equals(responseURI, &equal);
|
||||
if (!equal) {
|
||||
return RedirectForOpaqueResponse(responseURI);
|
||||
rv = RedirectForOpaqueResponse(responseURI);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return StartPump();
|
||||
rv = StartPump();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::FinishSynthesizedResponse()
|
||||
{
|
||||
if (mCanceled) {
|
||||
// Return NS_OK. The channel should fire callbacks with an error code
|
||||
// if it was cancelled before this point.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// TODO: Remove this API after interception moves to the parent process in
|
||||
// e10s mode.
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -747,23 +817,6 @@ InterceptedHttpChannel::CancelInterception(nsresult aStatus)
|
||||
return Cancel(aStatus);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::GetResponseBody(nsIOutputStream** aResponseBody)
|
||||
{
|
||||
if (!mBodyWriter) {
|
||||
nsresult rv = NS_NewPipe(getter_AddRefs(mBodyReader),
|
||||
getter_AddRefs(mBodyWriter),
|
||||
0, // default segment size
|
||||
UINT32_MAX, // infinite pipe length
|
||||
true, // non-blocking reader
|
||||
true); // non-blocking writer
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
nsCOMPtr<nsIOutputStream> ref(mBodyWriter);
|
||||
ref.forget(aResponseBody);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::GetChannel(nsIChannel** aChannel)
|
||||
{
|
||||
@ -931,6 +984,8 @@ InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv)
|
||||
Cancel(rv);
|
||||
}
|
||||
|
||||
MaybeCallBodyCallback();
|
||||
|
||||
mIsPending = false;
|
||||
ReleaseListeners();
|
||||
|
||||
@ -946,7 +1001,6 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest,
|
||||
if (!mProgressSink) {
|
||||
GetCallback(mProgressSink);
|
||||
}
|
||||
mTransactionTimings.responseStart = TimeStamp::Now();
|
||||
if (mListener) {
|
||||
mListener->OnStartRequest(this, mListenerContext);
|
||||
}
|
||||
@ -964,14 +1018,14 @@ InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest,
|
||||
mStatus = aStatus;
|
||||
}
|
||||
|
||||
MaybeCallBodyCallback();
|
||||
|
||||
// Its possible that we have any async runnable queued to report some
|
||||
// progress when OnStopRequest() is triggered. Report any left over
|
||||
// progress immediately. The extra runnable will then do nothing thanks
|
||||
// to the ReleaseListeners() call below.
|
||||
MaybeCallStatusAndProgress();
|
||||
|
||||
mTransactionTimings.responseEnd = TimeStamp::Now();
|
||||
|
||||
mIsPending = false;
|
||||
|
||||
// Register entry to the Performance resource timing
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user