merge mozilla-inbound to mozilla-central. r=merge a=merge

MozReview-Commit-ID: 790IXj5MZ4f
This commit is contained in:
Sebastian Hengst 2017-10-18 11:48:34 +02:00
commit f9b5b9b40c
121 changed files with 3352 additions and 2411 deletions

View File

@ -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));
}
}

View File

@ -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.
*/

View File

@ -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) {

View File

@ -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"

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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':

View 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
View File

@ -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(&currentVersion);
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(&currentVersion);
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

View File

@ -301,7 +301,7 @@ public:
}
nsresult
UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory) override
UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory) override
{
AssertIsOnIOThread();
MOZ_DIAGNOSTIC_ASSERT(aDirectory);

View 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]); });
});
}

View 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");
});
}

View File

@ -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);

View File

@ -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");
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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' });
});
};

View File

@ -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 {

View File

@ -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);
}

View File

@ -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.

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -99,7 +99,7 @@ public:
}
virtual nsresult
UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory)
UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory)
{
return NS_OK;
}

View File

@ -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);

View File

@ -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);

View File

@ -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]

View File

@ -0,0 +1,2 @@
<svg>
<set fill='freeze' dur='8' repeatCount='1844674737095516'>

After

Width:  |  Height:  |  Size: 65 B

View File

@ -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

View File

@ -6,7 +6,8 @@
#include "nsSMILTimeValue.h"
const nsSMILTime nsSMILTimeValue::kUnresolvedMillis = INT64_MAX;
const nsSMILTime nsSMILTimeValue::kUnresolvedMillis =
std::numeric_limits<nsSMILTime>::max();
//----------------------------------------------------------------------
// nsSMILTimeValue methods:

View File

@ -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);

View File

@ -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;

View File

@ -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);
});
});

View File

@ -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]

View File

@ -9,6 +9,7 @@ support-files =
serviceworkerinfo_iframe.html
serviceworkermanager_iframe.html
serviceworkerregistrationinfo_iframe.html
utils.js
worker.js
worker2.js

View File

@ -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());
}
});

View File

@ -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);

View File

@ -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.")

View File

@ -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 });
});
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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) {

View File

@ -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.

View File

@ -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).

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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>

View File

@ -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

View File

@ -0,0 +1,12 @@
<html>
<head>
<style>
*::-moz-list-bullet, * {
transform-style:preserve-3d;
}
</style>
</head>
<body>
<li></li>
</body>
</html>

View File

@ -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

View File

@ -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);

View File

@ -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++;

View File

@ -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);

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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");

View File

@ -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;

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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):

View File

@ -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

View File

@ -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.
*/

View File

@ -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

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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