Merge m-c to b2ginbound, a=merge

--HG--
extra : commitid : 4RZDM8UPfkA
This commit is contained in:
Wes Kocher 2015-10-26 14:38:51 -07:00
commit b91440b5e1
227 changed files with 5435 additions and 1936 deletions

View File

@ -30,19 +30,34 @@ function PresentationRequestUIGlue() {
SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => {
let detail = aEvent.detail;
if (detail.type != "presentation-receiver-launched") {
return;
}
switch (detail.type) {
case "presentation-receiver-launched": {
let sessionId = detail.id;
let resolver = this._resolvers[sessionId];
if (!resolver) {
debug("No correspondent resolver for session ID: " + sessionId);
return;
}
let sessionId = detail.id;
let resolver = this._resolvers[sessionId];
if (!resolver) {
debug("No correspondent resolver for session ID: " + sessionId);
return;
}
delete this._resolvers[sessionId];
resolver.resolve(detail.frame);
break;
}
case "presentation-receiver-permission-denied": {
let sessionId = detail.id;
let resolver = this._resolvers[sessionId];
if (!resolver) {
debug("No correspondent resolver for session ID: " + sessionId);
return;
}
delete this._resolvers[sessionId];
resolver(detail.frame);
delete this._resolvers[sessionId];
resolver.reject();
break;
}
default:
return;
}
});
}
@ -50,7 +65,10 @@ PresentationRequestUIGlue.prototype = {
sendRequest: function(aUrl, aSessionId) {
return new Promise(function(aResolve, aReject) {
this._resolvers[aSessionId] = aResolve;
this._resolvers[aSessionId] = {
resolve: aResolve,
reject: aReject,
};
SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
{ type: "presentation-launch-receiver",

View File

@ -20,16 +20,13 @@ SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
addMessageListener('trigger-ui-glue', function(aData) {
var promise = glue.sendRequest(aData.url, aData.sessionId);
promise.then(function(aFrame){
promise.then(function(aFrame) {
sendAsyncMessage('iframe-resolved', aFrame);
}).catch(function() {
sendAsyncMessage('iframe-rejected');
});
});
addMessageListener('trigger-presentation-content-event', function(aData) {
var detail = {
type: 'presentation-receiver-launched',
id: aData.sessionId,
frame: aData.frame
};
SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail);
addMessageListener('trigger-presentation-content-event', function(aDetail) {
SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', aDetail);
});

View File

@ -4,7 +4,7 @@
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset="utf-8">
<title>Test for Presentation Device Selection</title>
<title>Test for Presentation UI Glue</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
@ -57,14 +57,41 @@ function testReceiverLaunched() {
document.body.appendChild(iframe);
gScript.sendAsyncMessage('trigger-presentation-content-event',
{ sessionId : sessionId,
{ type: 'presentation-receiver-launched',
id: sessionId,
frame: iframe });
});
}
function testLaunchError() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
is(aDetail.url, url, "Url should be the same.");
is(aDetail.id, sessionId, "Session ID should be the same.");
gScript.addMessageListener('iframe-rejected', function iframeRejectedHandler() {
gScript.removeMessageListener('iframe-rejected', iframeRejectedHandler);
ok(true, "The promise should be rejected.");
aResolve();
});
gScript.sendAsyncMessage('trigger-presentation-content-event',
{ type: 'presentation-receiver-permission-denied',
id: sessionId });
});
gScript.sendAsyncMessage('trigger-ui-glue',
{ url: url,
sessionId : sessionId });
});
}
function runTests() {
testLaunchReceiver()
.then(testReceiverLaunched)
.then(testLaunchError)
.then(function() {
info('test finished, teardown');
gScript.destroy();

View File

@ -382,6 +382,8 @@
@RESPATH@/components/ConsoleAPIStorage.js
@RESPATH@/components/BrowserElementParent.manifest
@RESPATH@/components/BrowserElementParent.js
@RESPATH@/components/BrowserElementProxy.manifest
@RESPATH@/components/BrowserElementProxy.js
@RESPATH@/components/ContactManager.js
@RESPATH@/components/ContactManager.manifest
@RESPATH@/components/PhoneNumberService.js

View File

@ -307,8 +307,11 @@ var FullScreen = {
aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
FullScreen._isPopupOpen = true;
else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
aEvent.target.localName != "window")
aEvent.target.localName != "window") {
FullScreen._isPopupOpen = false;
// Try again to hide toolbar when we close the popup.
FullScreen.hideNavToolbox(true);
}
},
// Autohide helpers for the context menu item
@ -319,6 +322,8 @@ var FullScreen = {
setAutohide: function()
{
gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
// Try again to hide toolbar when we change the pref.
FullScreen.hideNavToolbox(true);
},
_WarningBox: {

View File

@ -70,7 +70,7 @@ PushSocket.prototype = {
let uri = Services.io.newURI(pushUri, null, null);
this._websocket.protocol = "push-notification";
this._websocket.asyncOpen(uri, pushUri, this, null);
this._websocket.asyncOpen(uri, pushUri, 0, this, null);
},
/**

View File

@ -157,11 +157,12 @@ MockWebSocketChannel.prototype = {
* nsIWebSocketChannel implementations.
* See nsIWebSocketChannel.idl for API details.
*/
asyncOpen: function(aURI, aOrigin, aListener, aContext) {
asyncOpen: function(aURI, aOrigin, aWindowId, aListener, aContext) {
this.uri = aURI;
this.origin = aOrigin;
this.listener = aListener;
this.context = aContext;
this.windowId = aWindowId;
this.listener.onStart(this.context);
},

View File

@ -365,6 +365,8 @@
@RESPATH@/components/ConsoleAPIStorage.js
@RESPATH@/components/BrowserElementParent.manifest
@RESPATH@/components/BrowserElementParent.js
@RESPATH@/components/BrowserElementProxy.manifest
@RESPATH@/components/BrowserElementProxy.js
@RESPATH@/components/FeedProcessor.manifest
@RESPATH@/components/FeedProcessor.js
@RESPATH@/components/PackagedAppUtils.js

View File

@ -17,11 +17,13 @@ if [ -d "$topsrcdir/clang" ]; then
export CC=$topsrcdir/clang/bin/clang
export CXX=$topsrcdir/clang/bin/clang++
export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
elif [ -d "$topsrcdir/../clang" ]; then
# comm-central based build
export CC=$topsrcdir/../clang/bin/clang
export CXX=$topsrcdir/../clang/bin/clang++
export LLVMCONFIG=$topsrcdir/../clang/bin/llvm-config
export DSYMUTIL=$topsrcdir/../clang/bin/llvm-dsymutil
fi
# If not set use the system default clang

View File

@ -463,31 +463,6 @@ GeneratedLocation.prototype = {
exports.GeneratedLocation = GeneratedLocation;
// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
// implemented.
exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
let bestOffsetMapping = null;
for (let offsetMapping of aScript.getAllColumnOffsets()) {
if (!bestOffsetMapping ||
(offsetMapping.offset <= aOffset &&
offsetMapping.offset > bestOffsetMapping.offset)) {
bestOffsetMapping = offsetMapping;
}
}
if (!bestOffsetMapping) {
// XXX: Try not to completely break the experience of using the debugger for
// the user by assuming column 0. Simultaneously, report the error so that
// there is a paper trail if the assumption is bad and the debugging
// experience becomes wonky.
reportError(new Error("Could not find a column for offset " + aOffset
+ " in the script " + aScript));
return 0;
}
return bestOffsetMapping.columnNumber;
}
/**
* A method decorator that ensures the actor is in the expected state before
* proceeding. If the actor is not in the expected state, the decorated method

View File

@ -3633,7 +3633,7 @@ function hackDebugger(Debugger) {
configurable: true,
get: function() {
if (this.script) {
return this.script.getOffsetLine(this.offset);
return this.script.getOffsetLocation(this.offset).lineNumber;
} else {
return null;
}

View File

@ -9,7 +9,7 @@ const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { dbg_assert, fetch } = DevToolsUtils;
const EventEmitter = require("devtools/shared/event-emitter");
const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
const { resolve } = require("promise");
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
@ -548,10 +548,12 @@ TabSources.prototype = {
if (!aFrame || !aFrame.script) {
return new GeneratedLocation();
}
let {lineNumber, columnNumber} =
aFrame.script.getOffsetLocation(aFrame.offset);
return new GeneratedLocation(
this.createNonSourceMappedActor(aFrame.script.source),
aFrame.script.getOffsetLine(aFrame.offset),
getOffsetColumn(aFrame.offset, aFrame.script)
lineNumber,
columnNumber
);
},

View File

@ -60,7 +60,7 @@ const testBlackBox = Task.async(function* () {
yield runTest(
function onSteppedLocation(aLocation) {
do_check_eq(aLocation.source.url, SOURCE_URL);
do_check_eq(aLocation.line, 3);
do_check_eq(aLocation.line, 4);
},
function onDebuggerStatementFrames(aFrames) {
for (let f of aFrames) {

View File

@ -131,6 +131,11 @@ this.PermissionsTable = { geolocation: {
privileged: ALLOW_ACTION,
certified: ALLOW_ACTION
},
"browser:embedded-system-app": {
app: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
bluetooth: {
app: DENY_ACTION,
privileged: DENY_ACTION,

View File

@ -126,7 +126,8 @@ public:
ErrorResult& aRv,
bool* aConnectionFailed);
void AsyncOpen(nsIPrincipal* aPrincipal, ErrorResult& aRv);
void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
ErrorResult& aRv);
nsresult ParseURL(const nsAString& aURL);
nsresult InitializeConnection(nsIPrincipal* aPrincipal);
@ -1124,6 +1125,7 @@ protected:
virtual bool InitWithWindow(nsPIDOMWindow* aWindow) override
{
AssertIsOnMainThread();
MOZ_ASSERT(aWindow);
nsIDocument* doc = aWindow->GetExtantDoc();
if (!doc) {
@ -1137,7 +1139,19 @@ protected:
return true;
}
mImpl->AsyncOpen(principal, mRv);
uint64_t windowID = 0;
nsCOMPtr<nsIDOMWindow> topWindow;
aWindow->GetScriptableTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
if (pTopWindow) {
pTopWindow = pTopWindow->GetCurrentInnerWindow();
}
if (pTopWindow) {
windowID = pTopWindow->WindowID();
}
mImpl->AsyncOpen(principal, windowID, mRv);
return true;
}
@ -1146,7 +1160,7 @@ protected:
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), mRv);
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv);
return true;
}
@ -1308,7 +1322,20 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
if (NS_IsMainThread()) {
MOZ_ASSERT(principal);
webSocket->mImpl->AsyncOpen(principal, aRv);
uint64_t windowID = 0;
nsCOMPtr<nsIDOMWindow> topWindow;
ownerWindow->GetScriptableTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
if (pTopWindow) {
pTopWindow = pTopWindow->GetCurrentInnerWindow();
}
if (pTopWindow) {
windowID = pTopWindow->WindowID();
}
webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
} else {
RefPtr<AsyncOpenRunnable> runnable =
new AsyncOpenRunnable(webSocket->mImpl, aRv);
@ -1604,7 +1631,8 @@ WebSocketImpl::Init(JSContext* aCx,
}
void
WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, ErrorResult& aRv)
WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
@ -1620,7 +1648,7 @@ WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, ErrorResult& aRv)
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
MOZ_ASSERT(!aRv.Failed());
aRv = mChannel->AsyncOpen(uri, asciiOrigin, this, nullptr);
aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
if (NS_WARN_IF(aRv.Failed())) {
return;
}

View File

@ -5356,14 +5356,6 @@ static void ProcessViewportToken(nsIDocument *aDocument,
#define IS_SEPARATOR(c) ((c == '=') || (c == ',') || (c == ';') || \
(c == '\t') || (c == '\n') || (c == '\r'))
/* static */
nsViewportInfo
nsContentUtils::GetViewportInfo(nsIDocument *aDocument,
const ScreenIntSize& aDisplaySize)
{
return aDocument->GetViewportInfo(aDisplaySize);
}
/* static */
nsresult
nsContentUtils::ProcessViewportInfo(nsIDocument *aDocument,
@ -7030,38 +7022,6 @@ nsContentUtils::GetSelectionInTextControl(Selection* aSelection,
aOutEndOffset = std::max(anchorOffset, focusOffset);
}
/* static */
nsRect
nsContentUtils::GetSelectionBoundingRect(Selection* aSel)
{
nsRect res;
// Bounding client rect may be empty after calling GetBoundingClientRect
// when range is collapsed. So we get caret's rect when range is
// collapsed.
if (aSel->IsCollapsed()) {
nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
if (frame) {
nsIFrame* relativeTo =
nsLayoutUtils::GetContainingBlockForClientRect(frame);
res = nsLayoutUtils::TransformFrameRectToAncestor(frame, res, relativeTo);
}
} else {
int32_t rangeCount = aSel->RangeCount();
nsLayoutUtils::RectAccumulator accumulator;
for (int32_t idx = 0; idx < rangeCount; ++idx) {
nsRange* range = aSel->GetRangeAt(idx);
nsRange::CollectClientRects(&accumulator, range,
range->GetStartParent(), range->StartOffset(),
range->GetEndParent(), range->EndOffset(),
true, false);
}
res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
accumulator.mResultRect;
}
return res;
}
nsIEditor*
nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext)

View File

@ -1672,24 +1672,6 @@ public:
*/
static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
/**
* Retrieve information about the viewport as a data structure.
* This will return information in the viewport META data section
* of the document. This can be used in lieu of ProcessViewportInfo(),
* which places the viewport information in the document header instead
* of returning it directly.
*
* @param aDisplayWidth width of the on-screen display area for this
* document, in device pixels.
* @param aDisplayHeight height of the on-screen display area for this
* document, in device pixels.
*
* NOTE: If the site is optimized for mobile (via the doctype), this
* will return viewport information that specifies default information.
*/
static nsViewportInfo GetViewportInfo(nsIDocument* aDocument,
const mozilla::ScreenIntSize& aDisplaySize);
// Call EnterMicroTask when you're entering JS execution.
// Usually the best way to do this is to use nsAutoMicroTask.
static void EnterMicroTask();
@ -2330,14 +2312,6 @@ public:
int32_t& aOutStartOffset,
int32_t& aOutEndOffset);
/**
* Takes a selection, and return selection's bounding rect which is relative
* to root frame.
*
* @param aSel Selection to check
*/
static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel);
/**
* Takes a frame for anonymous content within a text control (<input> or
* <textarea>), and returns an offset in the text content, adjusted for a

View File

@ -298,7 +298,7 @@ nsDOMWindowUtils::GetViewportInfo(uint32_t aDisplayWidth,
nsIDocument* doc = GetDocument();
NS_ENSURE_STATE(doc);
nsViewportInfo info = nsContentUtils::GetViewportInfo(doc, ScreenIntSize(aDisplayWidth, aDisplayHeight));
nsViewportInfo info = doc->GetViewportInfo(ScreenIntSize(aDisplayWidth, aDisplayHeight));
*aDefaultZoom = info.GetDefaultZoom().scale;
*aAllowZoom = info.IsZoomAllowed();
*aMinZoom = info.GetMinZoom().scale;

View File

@ -732,6 +732,19 @@ public:
*/
Element* GetRootElement() const;
/**
* Retrieve information about the viewport as a data structure.
* This will return information in the viewport META data section
* of the document. This can be used in lieu of ProcessViewportInfo(),
* which places the viewport information in the document header instead
* of returning it directly.
*
* @param aDisplaySize size of the on-screen display area for this
* document, in device pixels.
*
* NOTE: If the site is optimized for mobile (via the doctype), this
* will return viewport information that specifies default information.
*/
virtual nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize) = 0;
/**

View File

@ -19,7 +19,7 @@ static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);
/**
* Information retrieved from the <meta name="viewport"> tag. See
* nsContentUtils::GetViewportInfo for more information on this functionality.
* nsIDocument::GetViewportInfo for more information on this functionality.
*/
class MOZ_STACK_CLASS nsViewportInfo
{
@ -70,7 +70,7 @@ class MOZ_STACK_CLASS nsViewportInfo
/**
* Constrain the viewport calculations from the
* nsContentUtils::GetViewportInfo() function in order to always return
* nsIDocument::GetViewportInfo() function in order to always return
* sane minimum/maximum values.
*/
void ConstrainViewportValues();

View File

@ -26,3 +26,4 @@ run-if = os == 'linux'
[test_bug1008126.html]
run-if = os == 'linux'
[test_sandboxed_blob_uri.html]
[test_websocket_frame.html]

View File

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<!--
-->
<head>
<title>Basic websocket frame interception test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
</head>
<body>
<script class="testbody" type="text/javascript">
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
var frameReceivedCounter = 0;
var frameSentCounter = 0;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var tests = [
{ payload: "Hello world!" },
{ payload: (function() { var buffer = ""; for (var i = 0; i < 120; ++i) buffer += i; return buffer; }()) },
{ payload: "end" },
]
var innerId =
window.top.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
ok(innerId, "We have a valid innerWindowID: " + innerId);
var service = Cc["@mozilla.org/websocketframe/service;1"]
.getService(Ci.nsIWebSocketFrameService);
ok(!!service, "We have the nsIWebSocketFrameService");
var listener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebSocketFrameListener]),
frameReceived: function(aWebSocketSerialID, aFrame) {
ok(!!aFrame, "We have received a frame");
if (tests.length) {
ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, true, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
is(aFrame.maskBit, false, "Checking maskBit");
is(aFrame.mask, 0, "Checking mask");
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
} else {
ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, false, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
is(aFrame.maskBit, false, "Checking maskBit");
is(aFrame.mask, 0, "Checking mask");
}
frameReceivedCounter++;
},
frameSent: function(aWebSocketSerialID, aFrame) {
ok(!!aFrame, "We have sent a frame");
if (tests.length) {
ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, true, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
is(aFrame.maskBit, true, "Checking maskBit");
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
} else {
ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, false, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
is(aFrame.maskBit, true, "Checking maskBit");
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
}
frameSentCounter++;
}
};
service.addListener(innerId, listener);
ok(true, "Listener added");
function checkListener() {
service.removeListener(innerId, listener);
ok(frameReceivedCounter, "We received some frames!");
ok(frameSentCounter, "We sent some frames!");
SimpleTest.finish();
}
var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic", "frame");
ws.onopen = function(e) {
info("onopen");
ws.send(tests[0].payload);
}
ws.onclose = function(e) {
info("onclose");
ws.close();
checkListener();
}
ws.onmessage = function(e) {
info("onmessage");
is(e.data, tests[0].payload, "Wrong data");
tests.shift();
if (tests.length) {
ws.send(tests[0].payload);
}
}
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -85,6 +85,38 @@ const COMMAND_MAP = {
var global = this;
function BrowserElementProxyForwarder() {
}
BrowserElementProxyForwarder.prototype = {
init: function() {
Services.obs.addObserver(this, "browser-element-api:proxy-call", false);
addMessageListener("browser-element-api:proxy", this);
},
uninit: function() {
Services.obs.removeObserver(this, "browser-element-api:proxy-call", false);
removeMessageListener("browser-element-api:proxy", this);
},
// Observer callback receives messages from BrowserElementProxy.js
observe: function(subject, topic, stringifedData) {
if (subject !== content) {
return;
}
// Forward it to BrowserElementParent.js
sendAsyncMessage(topic, JSON.parse(stringifedData));
},
// Message manager callback receives messages from BrowserElementParent.js
receiveMessage: function(mmMsg) {
// Forward it to BrowserElementProxy.js
Services.obs.notifyObservers(
content, mmMsg.name, JSON.stringify(mmMsg.json));
}
};
function BrowserElementChild() {
// Maps outer window id --> weak ref to window. Used by modal dialog code.
this._windowIDDict = {};
@ -104,6 +136,8 @@ function BrowserElementChild() {
this._pendingSetInputMethodActive = [];
this._selectionStateChangedTarget = null;
this.forwarder = new BrowserElementProxyForwarder();
this._init();
};
@ -288,6 +322,8 @@ BrowserElementChild.prototype = {
OBSERVED_EVENTS.forEach((aTopic) => {
Services.obs.addObserver(this, aTopic, false);
});
this.forwarder.init();
},
observe: function(subject, topic, data) {
@ -327,6 +363,9 @@ BrowserElementChild.prototype = {
OBSERVED_EVENTS.forEach((aTopic) => {
Services.obs.removeObserver(this, aTopic);
});
this.forwarder.uninit();
this.forwarder = null;
},
get _windowUtils() {
@ -934,7 +973,7 @@ BrowserElementChild.prototype = {
},
_getSystemCtxMenuData: function(elem) {
let documentURI =
let documentURI =
docShell.QueryInterface(Ci.nsIWebNavigation).currentURI.spec;
if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
(elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {

View File

@ -64,6 +64,177 @@ function defineDOMRequestMethod(msgName) {
};
}
function BrowserElementParentProxyCallHandler() {
}
BrowserElementParentProxyCallHandler.prototype = {
_frameElement: null,
_mm: null,
MOZBROWSER_EVENT_NAMES: Object.freeze([
"loadstart", "loadend", "close", "error", "firstpaint",
"documentfirstpaint", "audioplaybackchange",
"contextmenu", "securitychange", "locationchange",
"iconchange", "scrollareachanged", "titlechange",
"opensearch", "manifestchange", "metachange",
"resize", "selectionstatechanged", "scrollviewchange",
"caretstatechanged", "activitydone", "scroll", "opentab"]),
init: function(frameElement, mm) {
this._frameElement = frameElement;
this._mm = mm;
this.innerWindowIDSet = new Set();
mm.addMessageListener("browser-element-api:proxy-call", this);
},
// Message manager callback receives messages from BrowserElementProxy.js
receiveMessage: function(mmMsg) {
let data = mmMsg.json;
let mm;
try {
mm = mmMsg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
} catch(e) {
mm = mmMsg.target;
}
if (!mm.assertPermission("browser:embedded-system-app")) {
dump("BrowserElementParent.js: Method call " + data.methodName +
" from a content process with no 'browser:embedded-system-app'" +
" privileges.\n");
return;
}
switch (data.methodName) {
case '_proxyInstanceInit':
if (!this.innerWindowIDSet.size) {
this._attachEventListeners();
}
this.innerWindowIDSet.add(data.innerWindowID);
break;
case '_proxyInstanceUninit':
this.innerWindowIDSet.delete(data.innerWindowID);
if (!this.innerWindowIDSet.size) {
this._detachEventListeners();
}
break;
// void methods
case 'setVisible':
case 'setActive':
case 'sendMouseEvent':
case 'sendTouchEvent':
case 'goBack':
case 'goForward':
case 'reload':
case 'stop':
case 'zoom':
case 'setNFCFocus':
case 'findAll':
case 'findNext':
case 'clearMatch':
case 'mute':
case 'unmute':
case 'setVolume':
this._frameElement[data.methodName]
.apply(this._frameElement, data.args);
break;
// DOMRequest methods
case 'getVisible':
case 'download':
case 'purgeHistory':
case 'getCanGoBack':
case 'getCanGoForward':
case 'getContentDimensions':
case 'setInputMethodActive':
case 'executeScript':
case 'getMuted':
case 'getVolume':
let req = this._frameElement[data.methodName]
.apply(this._frameElement, data.args);
req.onsuccess = () => {
this._sendToProxy({
domRequestId: data.domRequestId,
innerWindowID: data.innerWindowID,
result: req.result
});
};
req.onerror = () => {
this._sendToProxy({
domRequestId: data.domRequestId,
innerWindowID: data.innerWindowID,
err: req.error
});
};
break;
// Not implemented
case 'getActive': // Sync ???
case 'addNextPaintListener': // Takes a callback
case 'removeNextPaintListener': // Takes a callback
case 'getScreenshot': // Need to pass a blob back
dump("BrowserElementParentProxyCallHandler Error:" +
"Attempt to call unimplemented method " + data.methodName + ".\n");
break;
default:
dump("BrowserElementParentProxyCallHandler Error:" +
"Attempt to call non-exist method " + data.methodName + ".\n");
break;
}
},
// Receving events from the frame element and forward it.
handleEvent: function(evt) {
// Ignore the events from nested mozbrowser iframes
if (evt.target !== this._frameElement) {
return;
}
let detailString;
try {
detailString = JSON.stringify(evt.detail);
} catch (e) {
dump("BrowserElementParentProxyCallHandler Error:" +
"Event detail of " + evt.type + " can't be stingified.\n");
return;
}
this.innerWindowIDSet.forEach((innerWindowID) => {
this._sendToProxy({
eventName: evt.type,
innerWindowID: innerWindowID,
eventDetailString: detailString
});
});
},
_sendToProxy: function(data) {
this._mm.sendAsyncMessage("browser-element-api:proxy", data);
},
_attachEventListeners: function() {
this.MOZBROWSER_EVENT_NAMES.forEach(function(eventName) {
this._frameElement.addEventListener(
"mozbrowser" + eventName, this, true);
}, this);
},
_detachEventListeners: function() {
this.MOZBROWSER_EVENT_NAMES.forEach(function(eventName) {
this._frameElement.removeEventListener(
"mozbrowser" + eventName, this, true);
}, this);
}
};
function BrowserElementParent() {
debug("Creating new BrowserElementParent object");
this._domRequestCounter = 0;
@ -77,6 +248,8 @@ function BrowserElementParent() {
Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
this.proxyCallHandler = new BrowserElementParentProxyCallHandler();
}
BrowserElementParent.prototype = {
@ -123,6 +296,9 @@ BrowserElementParent.prototype = {
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
this._setupMessageListener();
this._registerAppManifest();
this.proxyCallHandler.init(
this._frameElement, this._frameLoader.messageManager);
},
_runPendingAPICall: function() {
@ -736,7 +912,7 @@ BrowserElementParent.prototype = {
if (!this._isAlive()) {
return null;
}
let uri = Services.io.newURI(_url, null, null);
let url = uri.QueryInterface(Ci.nsIURL);
@ -826,7 +1002,7 @@ BrowserElementParent.prototype = {
Ci.nsIRequestObserver])
};
// If we have a URI we'll use it to get the triggering principal to use,
// If we have a URI we'll use it to get the triggering principal to use,
// if not available a null principal is acceptable.
let referrer = null;
let principal = null;
@ -849,9 +1025,9 @@ BrowserElementParent.prototype = {
debug('Using principal? ' + !!principal);
let channel =
let channel =
Services.io.newChannelFromURI2(url,
null, // No document.
null, // No document.
principal, // Loading principal
principal, // Triggering principal
Ci.nsILoadInfo.SEC_NORMAL,
@ -872,7 +1048,7 @@ BrowserElementParent.prototype = {
channel.loadFlags |= flags;
if (channel instanceof Ci.nsIHttpChannel) {
debug('Setting HTTP referrer = ' + (referrer && referrer.spec));
debug('Setting HTTP referrer = ' + (referrer && referrer.spec));
channel.referrer = referrer;
if (channel instanceof Ci.nsIHttpChannelInternal) {
channel.forceAllowThirdPartyCookie = true;

View File

@ -0,0 +1,220 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
function defineNoReturnMethod(methodName) {
return function noReturnMethod() {
let args = Array.slice(arguments);
this._sendToParent(methodName, args);
};
}
function defineDOMRequestMethod(methodName) {
return function domRequestMethod() {
let args = Array.slice(arguments);
return this._sendDOMRequest(methodName, args);
};
}
function defineUnimplementedMethod(methodName) {
return function unimplementedMethod() {
throw Components.Exception(
'Unimplemented method: ' + methodName, Cr.NS_ERROR_FAILURE);
};
}
/**
* The BrowserElementProxy talks to the Browser IFrameElement instance on
* behave of the embedded document. It implements all the methods on
* the Browser IFrameElement and the methods will work if they are applicable.
*
* The message is forwarded to BrowserElementParent.js by creating an
* 'browser-element-api:proxy-call' observer message.
* BrowserElementChildPreload will get notified and send the message through
* to the main process through sendAsyncMessage with message of the same name.
*
* The return message will follow the same route. The message name on the
* return route is 'browser-element-api:proxy'.
*
* Both BrowserElementProxy and BrowserElementParent must be modified if there
* is a new method implemented, or a new event added to the Browser
* IFrameElement.
*
* Other details unmentioned here are checks of message sender and recipients
* to identify proxy instance on different innerWindows or ensure the content
* process has the right permission.
*/
function BrowserElementProxy() {
// Pad the 0th element so that DOMRequest ID will always be a truthy value.
this._pendingDOMRequests = [ undefined ];
}
BrowserElementProxy.prototype = {
classDescription: 'BrowserElementProxy allowed embedded frame to control ' +
'it\'s own embedding browser element frame instance.',
classID: Components.ID('{7e95d54c-9930-49c8-9a10-44fe40fe8251}'),
contractID: '@mozilla.org/dom/browser-element-proxy;1',
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver]),
_window: null,
_innerWindowID: undefined,
get allowedAudioChannels() {
return this._window.navigator.mozAudioChannelManager ?
this._window.navigator.mozAudioChannelManager.allowedAudioChannels :
null;
},
init: function(win) {
this._window = win;
this._innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.currentInnerWindowID;
this._sendToParent('_proxyInstanceInit');
Services.obs.addObserver(this, 'browser-element-api:proxy', false);
},
uninit: function(win) {
this._sendToParent('_proxyInstanceUninit');
this._window = null;
this._innerWindowID = undefined;
Services.obs.removeObserver(this, 'browser-element-api:proxy');
},
observe: function(subject, topic, stringifedData) {
let data = JSON.parse(stringifedData);
if (subject !== this._window ||
data.innerWindowID !== data.innerWindowID) {
return;
}
if (data.eventName) {
this._fireEvent(data.eventName, JSON.parse(data.eventDetailString));
return;
}
if ('domRequestId' in data) {
let req = this._pendingDOMRequests[data.domRequestId];
this._pendingDOMRequests[data.domRequestId] = undefined;
if (!req) {
dump('BrowserElementProxy Error: ' +
'Multiple observer messages for the same DOMRequest result.\n');
return;
}
if ('result' in data) {
let clientObj = Cu.cloneInto(data.result, this._window);
Services.DOMRequest.fireSuccess(req, clientObj);
} else {
let clientObj = Cu.cloneInto(data.error, this._window);
Services.DOMRequest.fireSuccess(req, clientObj);
}
return;
}
dump('BrowserElementProxy Error: ' +
'Received unhandled observer messages ' + stringifedData + '.\n');
},
_sendDOMRequest: function(methodName, args) {
let id = this._pendingDOMRequests.length;
let req = Services.DOMRequest.createRequest(this._window);
this._pendingDOMRequests.push(req);
this._sendToParent(methodName, args, id);
return req;
},
_sendToParent: function(methodName, args, domRequestId) {
let data = {
methodName: methodName,
args: args,
innerWindowID: this._innerWindowID
};
if (domRequestId) {
data.domRequestId = domRequestId;
}
Services.obs.notifyObservers(
this._window, 'browser-element-api:proxy-call', JSON.stringify(data));
},
_fireEvent: function(name, detail) {
let evt = this._createEvent(name, detail,
/* cancelable = */ false);
this.__DOM_IMPL__.dispatchEvent(evt);
},
_createEvent: function(evtName, detail, cancelable) {
// This will have to change if we ever want to send a CustomEvent with null
// detail. For now, it's OK.
if (detail !== undefined && detail !== null) {
detail = Cu.cloneInto(detail, this._window);
return new this._window.CustomEvent(evtName,
{ bubbles: false,
cancelable: cancelable,
detail: detail });
}
return new this._window.Event(evtName,
{ bubbles: false,
cancelable: cancelable });
},
setVisible: defineNoReturnMethod('setVisible'),
setActive: defineNoReturnMethod('setActive'),
sendMouseEvent: defineNoReturnMethod('sendMouseEvent'),
sendTouchEvent: defineNoReturnMethod('sendTouchEvent'),
goBack: defineNoReturnMethod('goBack'),
goForward: defineNoReturnMethod('goForward'),
reload: defineNoReturnMethod('reload'),
stop: defineNoReturnMethod('stop'),
zoom: defineNoReturnMethod('zoom'),
setNFCFocus: defineNoReturnMethod('setNFCFocus'),
findAll: defineNoReturnMethod('findAll'),
findNext: defineNoReturnMethod('findNext'),
clearMatch: defineNoReturnMethod('clearMatch'),
mute: defineNoReturnMethod('mute'),
unmute: defineNoReturnMethod('unmute'),
setVolume: defineNoReturnMethod('setVolume'),
getVisible: defineDOMRequestMethod('getVisible'),
download: defineDOMRequestMethod('download'),
purgeHistory: defineDOMRequestMethod('purgeHistory'),
getCanGoBack: defineDOMRequestMethod('getCanGoBack'),
getCanGoForward: defineDOMRequestMethod('getCanGoForward'),
getContentDimensions: defineDOMRequestMethod('getContentDimensions'),
setInputMethodActive: defineDOMRequestMethod('setInputMethodActive'),
executeScript: defineDOMRequestMethod('executeScript'),
getMuted: defineDOMRequestMethod('getMuted'),
getVolume: defineDOMRequestMethod('getVolume'),
getActive: defineUnimplementedMethod('getActive'),
addNextPaintListener: defineUnimplementedMethod('addNextPaintListener'),
removeNextPaintListener: defineUnimplementedMethod('removeNextPaintListener'),
getScreenshot: defineUnimplementedMethod('getScreenshot')
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementProxy]);

View File

@ -0,0 +1,2 @@
component {7e95d54c-9930-49c8-9a10-44fe40fe8251} BrowserElementProxy.js
contract @mozilla.org/dom/browser-element-proxy;1 {7e95d54c-9930-49c8-9a10-44fe40fe8251}

View File

@ -0,0 +1,161 @@
/* Any copyright is dedicated to the public domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
function runTest() {
let frameUrl = SimpleTest.getTestFileURL('/file_empty.html');
SpecialPowers.pushPermissions([{
type: 'browser:embedded-system-app',
allow: true,
context: {
url: frameUrl,
appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
isInBrowserElement: true
}
},{
type: 'browser',
allow: true,
context: {
url: frameUrl,
appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
isInBrowserElement: true
}
}], createFrame);
}
var frame;
var mm;
function createFrame() {
let loadEnd = function() {
// The frame script running in the frame where the input is hosted.
let appFrameScript = function appFrameScript() {
let i = 1;
addMessageListener('test:next', function() {
try {
switch (i) {
case 1:
content.document.addEventListener(
'visibilitychange', function fn() {
content.document.removeEventListener('visibilitychange', fn);
sendAsyncMessage('test:done', {
ok: true,
msg: 'setVisible()'
});
});
// Test a no return method
content.navigator.mozBrowserElementProxy.setVisible(false);
break;
case 2:
// Test a DOMRequest method
var req = content.navigator.mozBrowserElementProxy.getVisible();
req.onsuccess = function() {
sendAsyncMessage('test:done', {
ok: true,
msg: 'getVisible()'
});
};
req.onerror = function() {
sendAsyncMessage('test:done', {
ok: false,
msg: 'getVisible() DOMRequest return error.'
});
};
break;
case 3:
// Test an unimplemented method
try {
content.navigator.mozBrowserElementProxy.getActive();
sendAsyncMessage('test:done', {
ok: false,
msg: 'getActive() should throw.'
});
} catch (e) {
sendAsyncMessage('test:done', {
ok: true,
msg: 'getActive() throws.'
});
}
break;
case 4:
content.navigator.mozBrowserElementProxy
.addEventListener(
'mozbrowserlocationchange',
function() {
content.navigator.mozBrowserElementProxy
.onmozbrowserlocationchange = null;
sendAsyncMessage('test:done', {
ok: true,
msg: 'mozbrowserlocationchange'
});
});
// Test event
content.location.hash = '#foo';
break;
}
} catch (e) {
sendAsyncMessage('test:done', {
ok: false, msg: 'thrown: ' + e.toString() });
}
i++;
});
sendAsyncMessage('test:done', {});
}
mm = SpecialPowers.getBrowserFrameMessageManager(frame);
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
mm.addMessageListener("test:done", next);
};
frame = document.createElement('iframe');
frame.setAttribute('mozbrowser', 'true');
frame.src = 'file_empty.html';
document.body.appendChild(frame);
frame.addEventListener('mozbrowserloadend', loadEnd);
}
var i = 0;
function next(msg) {
let wrappedMsg = SpecialPowers.wrap(msg);
let isOK = wrappedMsg.data.ok;
let msgString = wrappedMsg.data.msg;
switch (i) {
case 0:
mm.sendAsyncMessage('test:next');
break;
case 1:
case 2:
case 3:
ok(isOK, msgString);
mm.sendAsyncMessage('test:next');
break;
case 4:
ok(isOK, msgString);
SimpleTest.finish();
break;
}
i++;
}
addEventListener('testready', runTest);

View File

@ -76,6 +76,8 @@ skip-if = (toolkit == 'gonk') # Disabled on emulator. See bug 1144015 comment 8
[test_browserElement_oop_PrivateBrowsing.html]
[test_browserElement_oop_PromptCheck.html]
[test_browserElement_oop_PromptConfirm.html]
[test_browserElement_oop_Proxy.html]
skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
[test_browserElement_oop_PurgeHistory.html]
[test_browserElement_oop_Reload.html]
[test_browserElement_oop_ReloadPostRequest.html]

View File

@ -54,6 +54,7 @@ support-files =
browserElement_PrivateBrowsing.js
browserElement_PromptCheck.js
browserElement_PromptConfirm.js
browserElement_Proxy.js
browserElement_PurgeHistory.js
browserElement_Reload.js
browserElement_ReloadPostRequest.js
@ -204,6 +205,8 @@ skip-if = (toolkit == 'gonk' && !debug)
[test_browserElement_inproc_PrivateBrowsing.html]
[test_browserElement_inproc_PromptCheck.html]
[test_browserElement_inproc_PromptConfirm.html]
[test_browserElement_inproc_Proxy.html]
skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
[test_browserElement_inproc_PurgeHistory.html]
[test_browserElement_inproc_ReloadPostRequest.html]
[test_browserElement_inproc_RemoveBrowserElement.html]

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1196654
-->
<head>
<title>Test for Bug 1196654</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="browserElementTestHelpers.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=1196654">Mozilla Bug 1196654</a>
<script type="application/javascript;version=1.7" src="browserElement_Proxy.js">
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1196654
-->
<head>
<title>Test for Bug 1196654</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="browserElementTestHelpers.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=1196654">Mozilla Bug 1196654</a>
<script type="application/javascript;version=1.7" src="browserElement_Proxy.js">
</script>
</body>
</html>

View File

@ -26,6 +26,8 @@ XPIDL_MODULE = 'browser-element'
EXTRA_COMPONENTS += [
'BrowserElementParent.js',
'BrowserElementParent.manifest',
'BrowserElementProxy.js',
'BrowserElementProxy.manifest',
]
EXTRA_JS_MODULES += [

View File

@ -454,395 +454,6 @@ ExtractFromURLSearchParams(const URLSearchParams& aParams,
aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
return NS_NewStringInputStream(aStream, serialized);
}
class MOZ_STACK_CLASS FillFormIterator final
: public URLSearchParams::ForEachIterator
{
public:
explicit FillFormIterator(nsFormData* aFormData)
: mFormData(aFormData)
{
MOZ_ASSERT(aFormData);
}
bool URLParamsIterator(const nsString& aName,
const nsString& aValue) override
{
mFormData->Append(aName, aValue);
return true;
}
private:
nsFormData* mFormData;
};
/**
* A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
* This does not respect any encoding specified per entry, using UTF-8
* throughout. This is as the Fetch spec states in the consume body algorithm.
* Borrows some things from Necko's nsMultiMixedConv, but is simpler since
* unlike Necko we do not have to deal with receiving incomplete chunks of data.
*
* This parser will fail the entire parse on any invalid entry, so it will
* never return a partially filled FormData.
* The content-disposition header is used to figure out the name and filename
* entries. The inclusion of the filename parameter decides if the entry is
* inserted into the nsFormData as a string or a File.
*
* File blobs are copies of the underlying data string since we cannot adopt
* char* chunks embedded within the larger body without significant effort.
* FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
* friends to figure out if Fetch ends up copying big blobs to see if this is
* worth optimizing.
*/
class MOZ_STACK_CLASS FormDataParser
{
private:
RefPtr<nsFormData> mFormData;
nsCString mMimeType;
nsCString mData;
// Entry state, reset in START_PART.
nsCString mName;
nsCString mFilename;
nsCString mContentType;
enum
{
START_PART,
PARSE_HEADER,
PARSE_BODY,
} mState;
nsIGlobalObject* mParentObject;
// Reads over a boundary and sets start to the position after the end of the
// boundary. Returns false if no boundary is found immediately.
bool
PushOverBoundary(const nsACString& aBoundaryString,
nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
// We copy the end iterator to keep the original pointing to the real end
// of the string.
nsACString::const_iterator end(aEnd);
const char* beginning = aStart.get();
if (FindInReadable(aBoundaryString, aStart, end)) {
// We either should find the body immediately, or after 2 chars with the
// 2 chars being '-', everything else is failure.
if ((aStart.get() - beginning) == 0) {
aStart.advance(aBoundaryString.Length());
return true;
}
if ((aStart.get() - beginning) == 2) {
if (*(--aStart) == '-' && *(--aStart) == '-') {
aStart.advance(aBoundaryString.Length() + 2);
return true;
}
}
}
return false;
}
// Reads over a CRLF and positions start after it.
bool
PushOverLine(nsACString::const_iterator& aStart)
{
if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
++aStart; // advance to after CRLF
return true;
}
return false;
}
bool
FindCRLF(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
nsACString::const_iterator end(aEnd);
return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
}
bool
ParseHeader(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd,
bool* aWasEmptyHeader)
{
MOZ_ASSERT(aWasEmptyHeader);
// Set it to a valid value here so we don't forget later.
*aWasEmptyHeader = false;
const char* beginning = aStart.get();
nsACString::const_iterator end(aEnd);
if (!FindCRLF(aStart, end)) {
return false;
}
if (aStart.get() == beginning) {
*aWasEmptyHeader = true;
return true;
}
nsAutoCString header(beginning, aStart.get() - beginning);
nsACString::const_iterator headerStart, headerEnd;
header.BeginReading(headerStart);
header.EndReading(headerEnd);
if (!FindCharInReadable(':', headerStart, headerEnd)) {
return false;
}
nsAutoCString headerName(StringHead(header, headerStart.size_backward()));
headerName.CompressWhitespace();
if (!NS_IsValidHTTPToken(headerName)) {
return false;
}
nsAutoCString headerValue(Substring(++headerStart, headerEnd));
if (!NS_IsReasonableHTTPHeaderValue(headerValue)) {
return false;
}
headerValue.CompressWhitespace();
if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
bool seenFormData = false;
while (tokenizer.hasMoreTokens()) {
const nsDependentCSubstring& token = tokenizer.nextToken();
if (token.IsEmpty()) {
continue;
}
if (token.EqualsLiteral("form-data")) {
seenFormData = true;
continue;
}
if (seenFormData &&
StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
mName = StringTail(token, token.Length() - 5);
mName.Trim(" \"");
continue;
}
if (seenFormData &&
StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
mFilename = StringTail(token, token.Length() - 9);
mFilename.Trim(" \"");
continue;
}
}
if (mName.IsVoid()) {
// Could not parse a valid entry name.
return false;
}
} else if (headerName.LowerCaseEqualsLiteral("content-type")) {
mContentType = headerValue;
}
return true;
}
// The end of a body is marked by a CRLF followed by the boundary. So the
// CRLF is part of the boundary and not the body, but any prior CRLFs are
// part of the body. This will position the iterator at the beginning of the
// boundary (after the CRLF).
bool
ParseBody(const nsACString& aBoundaryString,
nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
const char* beginning = aStart.get();
// Find the boundary marking the end of the body.
nsACString::const_iterator end(aEnd);
if (!FindInReadable(aBoundaryString, aStart, end)) {
return false;
}
// We found a boundary, strip the just prior CRLF, and consider
// everything else the body section.
if (aStart.get() - beginning < 2) {
// Only the first entry can have a boundary right at the beginning. Even
// an empty body will have a CRLF before the boundary. So this is
// a failure.
return false;
}
// Check that there is a CRLF right before the boundary.
aStart.advance(-2);
// Skip optional hyphens.
if (*aStart == '-' && *(aStart.get()+1) == '-') {
if (aStart.get() - beginning < 2) {
return false;
}
aStart.advance(-2);
}
if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
return false;
}
nsAutoCString body(beginning, aStart.get() - beginning);
// Restore iterator to after the \r\n as we promised.
// We do not need to handle the extra hyphens case since our boundary
// parser in PushOverBoundary()
aStart.advance(2);
if (!mFormData) {
mFormData = new nsFormData();
}
NS_ConvertUTF8toUTF16 name(mName);
if (mFilename.IsVoid()) {
mFormData->Append(name, NS_ConvertUTF8toUTF16(body));
} else {
// Unfortunately we've to copy the data first since all our strings are
// going to free it. We also need fallible alloc, so we can't just use
// ToNewCString().
char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
if (!copy) {
NS_WARNING("Failed to copy File entry body.");
return false;
}
nsCString::const_iterator bodyIter, bodyEnd;
body.BeginReading(bodyIter);
body.EndReading(bodyEnd);
char *p = copy;
while (bodyIter != bodyEnd) {
*p++ = *bodyIter++;
}
p = nullptr;
RefPtr<Blob> file =
File::CreateMemoryFile(mParentObject,
reinterpret_cast<void *>(copy), body.Length(),
NS_ConvertUTF8toUTF16(mFilename),
NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
Optional<nsAString> dummy;
mFormData->Append(name, *file, dummy);
}
return true;
}
public:
FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
: mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
{
}
bool
Parse()
{
// Determine boundary from mimetype.
const char* boundaryId = nullptr;
boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
if (!boundaryId) {
return false;
}
boundaryId = strchr(boundaryId, '=');
if (!boundaryId) {
return false;
}
// Skip over '='.
boundaryId++;
char *attrib = (char *) strchr(boundaryId, ';');
if (attrib) *attrib = '\0';
nsAutoCString boundaryString(boundaryId);
if (attrib) *attrib = ';';
boundaryString.Trim(" \"");
if (boundaryString.Length() == 0) {
return false;
}
nsACString::const_iterator start, end;
mData.BeginReading(start);
// This should ALWAYS point to the end of data.
// Helpers make copies.
mData.EndReading(end);
while (start != end) {
switch(mState) {
case START_PART:
mName.SetIsVoid(true);
mFilename.SetIsVoid(true);
mContentType = NS_LITERAL_CSTRING("text/plain");
// MUST start with boundary.
if (!PushOverBoundary(boundaryString, start, end)) {
return false;
}
if (start != end && *start == '-') {
// End of data.
if (!mFormData) {
mFormData = new nsFormData();
}
return true;
}
if (!PushOverLine(start)) {
return false;
}
mState = PARSE_HEADER;
break;
case PARSE_HEADER:
bool emptyHeader;
if (!ParseHeader(start, end, &emptyHeader)) {
return false;
}
if (!PushOverLine(start)) {
return false;
}
mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
break;
case PARSE_BODY:
if (mName.IsVoid()) {
NS_WARNING("No content-disposition header with a valid name was "
"found. Failing at body parse.");
return false;
}
if (!ParseBody(boundaryString, start, end)) {
return false;
}
mState = START_PART;
break;
default:
MOZ_CRASH("Invalid case");
}
}
NS_NOTREACHED("Should never reach here.");
return false;
}
already_AddRefed<nsFormData> FormData()
{
return mFormData.forget();
}
};
} // namespace
nsresult

View File

@ -122,6 +122,418 @@ FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
return blob.forget();
}
static bool
FindCRLF(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
nsACString::const_iterator end(aEnd);
return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
}
// Reads over a CRLF and positions start after it.
static bool
PushOverLine(nsACString::const_iterator& aStart)
{
if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
++aStart; // advance to after CRLF
return true;
}
return false;
}
// static
bool
FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd,
nsCString& aHeaderName,
nsCString& aHeaderValue,
bool* aWasEmptyHeader)
{
MOZ_ASSERT(aWasEmptyHeader);
// Set it to a valid value here so we don't forget later.
*aWasEmptyHeader = false;
const char* beginning = aStart.get();
nsACString::const_iterator end(aEnd);
if (!FindCRLF(aStart, end)) {
return false;
}
if (aStart.get() == beginning) {
*aWasEmptyHeader = true;
return true;
}
nsAutoCString header(beginning, aStart.get() - beginning);
nsACString::const_iterator headerStart, headerEnd;
header.BeginReading(headerStart);
header.EndReading(headerEnd);
if (!FindCharInReadable(':', headerStart, headerEnd)) {
return false;
}
aHeaderName.Assign(StringHead(header, headerStart.size_backward()));
aHeaderName.CompressWhitespace();
if (!NS_IsValidHTTPToken(aHeaderName)) {
return false;
}
aHeaderValue.Assign(Substring(++headerStart, headerEnd));
if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
return false;
}
aHeaderValue.CompressWhitespace();
return PushOverLine(aStart);
}
namespace {
class MOZ_STACK_CLASS FillFormIterator final
: public URLSearchParams::ForEachIterator
{
public:
explicit FillFormIterator(nsFormData* aFormData)
: mFormData(aFormData)
{
MOZ_ASSERT(aFormData);
}
bool URLParamsIterator(const nsString& aName,
const nsString& aValue) override
{
mFormData->Append(aName, aValue);
return true;
}
private:
nsFormData* mFormData;
};
/**
* A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
* This does not respect any encoding specified per entry, using UTF-8
* throughout. This is as the Fetch spec states in the consume body algorithm.
* Borrows some things from Necko's nsMultiMixedConv, but is simpler since
* unlike Necko we do not have to deal with receiving incomplete chunks of data.
*
* This parser will fail the entire parse on any invalid entry, so it will
* never return a partially filled FormData.
* The content-disposition header is used to figure out the name and filename
* entries. The inclusion of the filename parameter decides if the entry is
* inserted into the nsFormData as a string or a File.
*
* File blobs are copies of the underlying data string since we cannot adopt
* char* chunks embedded within the larger body without significant effort.
* FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
* friends to figure out if Fetch ends up copying big blobs to see if this is
* worth optimizing.
*/
class MOZ_STACK_CLASS FormDataParser
{
private:
RefPtr<nsFormData> mFormData;
nsCString mMimeType;
nsCString mData;
// Entry state, reset in START_PART.
nsCString mName;
nsCString mFilename;
nsCString mContentType;
enum
{
START_PART,
PARSE_HEADER,
PARSE_BODY,
} mState;
nsIGlobalObject* mParentObject;
// Reads over a boundary and sets start to the position after the end of the
// boundary. Returns false if no boundary is found immediately.
bool
PushOverBoundary(const nsACString& aBoundaryString,
nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
// We copy the end iterator to keep the original pointing to the real end
// of the string.
nsACString::const_iterator end(aEnd);
const char* beginning = aStart.get();
if (FindInReadable(aBoundaryString, aStart, end)) {
// We either should find the body immediately, or after 2 chars with the
// 2 chars being '-', everything else is failure.
if ((aStart.get() - beginning) == 0) {
aStart.advance(aBoundaryString.Length());
return true;
}
if ((aStart.get() - beginning) == 2) {
if (*(--aStart) == '-' && *(--aStart) == '-') {
aStart.advance(aBoundaryString.Length() + 2);
return true;
}
}
}
return false;
}
bool
ParseHeader(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd,
bool* aWasEmptyHeader)
{
nsAutoCString headerName, headerValue;
if (!FetchUtil::ExtractHeader(aStart, aEnd,
headerName, headerValue,
aWasEmptyHeader)) {
return false;
}
if (*aWasEmptyHeader) {
return true;
}
if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
bool seenFormData = false;
while (tokenizer.hasMoreTokens()) {
const nsDependentCSubstring& token = tokenizer.nextToken();
if (token.IsEmpty()) {
continue;
}
if (token.EqualsLiteral("form-data")) {
seenFormData = true;
continue;
}
if (seenFormData &&
StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
mName = StringTail(token, token.Length() - 5);
mName.Trim(" \"");
continue;
}
if (seenFormData &&
StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
mFilename = StringTail(token, token.Length() - 9);
mFilename.Trim(" \"");
continue;
}
}
if (mName.IsVoid()) {
// Could not parse a valid entry name.
return false;
}
} else if (headerName.LowerCaseEqualsLiteral("content-type")) {
mContentType = headerValue;
}
return true;
}
// The end of a body is marked by a CRLF followed by the boundary. So the
// CRLF is part of the boundary and not the body, but any prior CRLFs are
// part of the body. This will position the iterator at the beginning of the
// boundary (after the CRLF).
bool
ParseBody(const nsACString& aBoundaryString,
nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd)
{
const char* beginning = aStart.get();
// Find the boundary marking the end of the body.
nsACString::const_iterator end(aEnd);
if (!FindInReadable(aBoundaryString, aStart, end)) {
return false;
}
// We found a boundary, strip the just prior CRLF, and consider
// everything else the body section.
if (aStart.get() - beginning < 2) {
// Only the first entry can have a boundary right at the beginning. Even
// an empty body will have a CRLF before the boundary. So this is
// a failure.
return false;
}
// Check that there is a CRLF right before the boundary.
aStart.advance(-2);
// Skip optional hyphens.
if (*aStart == '-' && *(aStart.get()+1) == '-') {
if (aStart.get() - beginning < 2) {
return false;
}
aStart.advance(-2);
}
if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
return false;
}
nsAutoCString body(beginning, aStart.get() - beginning);
// Restore iterator to after the \r\n as we promised.
// We do not need to handle the extra hyphens case since our boundary
// parser in PushOverBoundary()
aStart.advance(2);
if (!mFormData) {
mFormData = new nsFormData();
}
NS_ConvertUTF8toUTF16 name(mName);
if (mFilename.IsVoid()) {
mFormData->Append(name, NS_ConvertUTF8toUTF16(body));
} else {
// Unfortunately we've to copy the data first since all our strings are
// going to free it. We also need fallible alloc, so we can't just use
// ToNewCString().
char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
if (!copy) {
NS_WARNING("Failed to copy File entry body.");
return false;
}
nsCString::const_iterator bodyIter, bodyEnd;
body.BeginReading(bodyIter);
body.EndReading(bodyEnd);
char *p = copy;
while (bodyIter != bodyEnd) {
*p++ = *bodyIter++;
}
p = nullptr;
RefPtr<Blob> file =
File::CreateMemoryFile(mParentObject,
reinterpret_cast<void *>(copy), body.Length(),
NS_ConvertUTF8toUTF16(mFilename),
NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
Optional<nsAString> dummy;
mFormData->Append(name, *file, dummy);
}
return true;
}
public:
FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
: mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
{
}
bool
Parse()
{
// Determine boundary from mimetype.
const char* boundaryId = nullptr;
boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
if (!boundaryId) {
return false;
}
boundaryId = strchr(boundaryId, '=');
if (!boundaryId) {
return false;
}
// Skip over '='.
boundaryId++;
char *attrib = (char *) strchr(boundaryId, ';');
if (attrib) *attrib = '\0';
nsAutoCString boundaryString(boundaryId);
if (attrib) *attrib = ';';
boundaryString.Trim(" \"");
if (boundaryString.Length() == 0) {
return false;
}
nsACString::const_iterator start, end;
mData.BeginReading(start);
// This should ALWAYS point to the end of data.
// Helpers make copies.
mData.EndReading(end);
while (start != end) {
switch(mState) {
case START_PART:
mName.SetIsVoid(true);
mFilename.SetIsVoid(true);
mContentType = NS_LITERAL_CSTRING("text/plain");
// MUST start with boundary.
if (!PushOverBoundary(boundaryString, start, end)) {
return false;
}
if (start != end && *start == '-') {
// End of data.
if (!mFormData) {
mFormData = new nsFormData();
}
return true;
}
if (!PushOverLine(start)) {
return false;
}
mState = PARSE_HEADER;
break;
case PARSE_HEADER:
bool emptyHeader;
if (!ParseHeader(start, end, &emptyHeader)) {
return false;
}
if (emptyHeader && !PushOverLine(start)) {
return false;
}
mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
break;
case PARSE_BODY:
if (mName.IsVoid()) {
NS_WARNING("No content-disposition header with a valid name was "
"found. Failing at body parse.");
return false;
}
if (!ParseBody(boundaryString, start, end)) {
return false;
}
mState = START_PART;
break;
default:
MOZ_CRASH("Invalid case");
}
}
NS_NOTREACHED("Should never reach here.");
return false;
}
already_AddRefed<nsFormData> FormData()
{
return mFormData.forget();
}
};
}
// static
already_AddRefed<nsFormData>
FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,

View File

@ -65,6 +65,16 @@ public:
static void
ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
const nsString& aStr, ErrorResult& aRv);
/**
* Extracts an HTTP header from a substring range.
*/
static bool
ExtractHeader(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd,
nsCString& aHeaderName,
nsCString& aHeaderValue,
bool* aWasEmptyHeader);
};
} // namespace dom

View File

@ -694,16 +694,10 @@ nsFSTextPlain::GetEncodedSubmission(nsIURI* aURI,
nsEncodingFormSubmission::nsEncodingFormSubmission(const nsACString& aCharset,
nsIContent* aOriginatingElement)
: nsFormSubmission(aCharset, aOriginatingElement)
, mEncoder(aCharset)
{
nsAutoCString charset(aCharset);
// canonical name is passed so that we just have to check against
// *our* canonical names listed in charsetaliases.properties
if (charset.EqualsLiteral("ISO-8859-1")) {
charset.AssignLiteral("windows-1252");
}
if (!(charset.EqualsLiteral("UTF-8") || charset.EqualsLiteral("gb18030"))) {
NS_ConvertUTF8toUTF16 charsetUtf16(charset);
if (!(aCharset.EqualsLiteral("UTF-8") || aCharset.EqualsLiteral("gb18030"))) {
NS_ConvertUTF8toUTF16 charsetUtf16(aCharset);
const char16_t* charsetPtr = charsetUtf16.get();
SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument()
: nullptr,
@ -711,18 +705,6 @@ nsEncodingFormSubmission::nsEncodingFormSubmission(const nsACString& aCharset,
&charsetPtr,
1);
}
mEncoder = do_CreateInstance(NS_SAVEASCHARSET_CONTRACTID);
if (mEncoder) {
nsresult rv =
mEncoder->Init(charset.get(),
(nsISaveAsCharset::attr_EntityAfterCharsetConv +
nsISaveAsCharset::attr_FallbackDecimalNCR),
0);
if (NS_FAILED(rv)) {
mEncoder = nullptr;
}
}
}
nsEncodingFormSubmission::~nsEncodingFormSubmission()
@ -734,15 +716,8 @@ nsresult
nsEncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut,
bool aHeaderEncode)
{
if (mEncoder && !aStr.IsEmpty()) {
aOut.Truncate();
nsresult rv = mEncoder->Convert(PromiseFlatString(aStr).get(),
getter_Copies(aOut));
NS_ENSURE_SUCCESS(rv, rv);
}
else {
// fall back to UTF-8
CopyUTF16toUTF8(aStr, aOut);
if (!mEncoder.Encode(aStr, aOut)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (aHeaderEncode) {

View File

@ -10,11 +10,11 @@
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsIContent.h"
#include "nsNCRFallbackEncoderWrapper.h"
class nsIURI;
class nsIInputStream;
class nsGenericHTMLElement;
class nsISaveAsCharset;
class nsIMultiplexInputStream;
namespace mozilla {
@ -140,7 +140,7 @@ public:
private:
// The encoder that will encode Unicode names and values
nsCOMPtr<nsISaveAsCharset> mEncoder;
nsNCRFallbackEncoderWrapper mEncoder;
};
/**

View File

@ -17,13 +17,13 @@
#ifdef PR_LOGGING
PRLogModuleInfo* gMP3DemuxerLog;
#define MP3DEMUXER_LOG(msg, ...) \
#define MP3LOG(msg, ...) \
MOZ_LOG(gMP3DemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__))
#define MP3DEMUXER_LOGV(msg, ...) \
#define MP3LOGV(msg, ...) \
MOZ_LOG(gMP3DemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__))
#else
#define MP3DEMUXER_LOG(msg, ...)
#define MP3DEMUXER_LOGV(msg, ...)
#define MP3LOG(msg, ...)
#define MP3LOGV(msg, ...)
#endif
using mozilla::media::TimeUnit;
@ -50,13 +50,13 @@ MP3Demuxer::InitInternal() {
RefPtr<MP3Demuxer::InitPromise>
MP3Demuxer::Init() {
if (!InitInternal()) {
MP3DEMUXER_LOG("MP3Demuxer::Init() failure: waiting for data");
MP3LOG("MP3Demuxer::Init() failure: waiting for data");
return InitPromise::CreateAndReject(
DemuxerFailureReason::DEMUXER_ERROR, __func__);
}
MP3DEMUXER_LOG("MP3Demuxer::Init() successful");
MP3LOG("MP3Demuxer::Init() successful");
return InitPromise::CreateAndResolve(NS_OK, __func__);
}
@ -87,14 +87,14 @@ void
MP3Demuxer::NotifyDataArrived() {
// TODO: bug 1169485.
NS_WARNING("Unimplemented function NotifyDataArrived");
MP3DEMUXER_LOGV("NotifyDataArrived()");
MP3LOGV("NotifyDataArrived()");
}
void
MP3Demuxer::NotifyDataRemoved() {
// TODO: bug 1169485.
NS_WARNING("Unimplemented function NotifyDataRemoved");
MP3DEMUXER_LOGV("NotifyDataRemoved()");
MP3LOGV("NotifyDataRemoved()");
}
@ -102,6 +102,14 @@ MP3Demuxer::NotifyDataRemoved() {
MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource)
: mSource(aSource)
, mOffset(0)
, mFirstFrameOffset(0)
, mNumParsedFrames(0)
, mFrameIndex(0)
, mTotalFrameLen(0)
, mSamplesPerFrame(0)
, mSamplesPerSecond(0)
, mChannels(0)
{
Reset();
@ -119,8 +127,8 @@ MP3TrackDemuxer::Init() {
// Read the first frame to fetch sample rate and other meta data.
RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
MP3DEMUXER_LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
StreamLength(), !!frame);
MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
StreamLength(), !!frame);
if (!frame) {
return false;
@ -139,9 +147,9 @@ MP3TrackDemuxer::Init() {
mInfo->mMimeType = "audio/mpeg";
mInfo->mDuration = Duration().ToMicroseconds();
MP3DEMUXER_LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}",
mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
mInfo->mDuration);
MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}",
mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
mInfo->mDuration);
return mSamplesPerSecond && mChannels;
}
@ -180,50 +188,55 @@ MP3TrackDemuxer::GetInfo() const {
RefPtr<MP3TrackDemuxer::SeekPromise>
MP3TrackDemuxer::Seek(TimeUnit aTime) {
// Efficiently seek to the position.
FastSeek(aTime);
// Correct seek position by scanning the next frames.
const TimeUnit seekTime = ScanUntil(aTime);
return SeekPromise::CreateAndResolve(seekTime, __func__);
}
TimeUnit
MP3TrackDemuxer::FastSeek(TimeUnit aTime) {
MP3DEMUXER_LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mOffset);
MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) {
MP3LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
const auto& vbr = mParser.VBRInfo();
if (!aTime.ToMicroseconds()) {
// Quick seek to the beginning of the stream.
mOffset = mFirstFrameOffset;
mFrameIndex = 0;
mParser.EndFrameSession();
return TimeUnit();
} else if (vbr.IsTOCPresent()) {
// Use TOC for more precise seeking.
const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) /
Duration().ToMicroseconds();
mOffset = vbr.Offset(durationFrac);
} else if (AverageFrameLength() > 0) {
mOffset = mFirstFrameOffset + FrameIndexFromTime(aTime) *
AverageFrameLength();
}
if (!mSamplesPerFrame || !mNumParsedFrames) {
return TimeUnit::FromMicroseconds(-1);
if (mOffset > mFirstFrameOffset && StreamLength() > 0) {
mOffset = std::min(StreamLength() - 1, mOffset);
}
const int64_t numFrames = aTime.ToSeconds() *
mSamplesPerSecond / mSamplesPerFrame;
mOffset = mFirstFrameOffset + numFrames * AverageFrameLength();
mFrameIndex = numFrames;
MP3DEMUXER_LOG("FastSeek mSamplesPerSecond=%d mSamplesPerFrame=%d "
"numFrames=%" PRId64,
mSamplesPerSecond, mSamplesPerFrame, numFrames);
mFrameIndex = FrameIndexFromOffset(mOffset);
mParser.EndFrameSession();
MP3LOG("FastSeek End TOC=%d avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mFirstFrameOffset=%llu mOffset=%" PRIu64
" SL=%llu NumBytes=%u",
vbr.IsTOCPresent(), AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mFirstFrameOffset, mOffset, StreamLength(), vbr.NumBytes().valueOr(0));
return Duration(mFrameIndex);
}
TimeUnit
MP3TrackDemuxer::ScanUntil(TimeUnit aTime) {
MP3DEMUXER_LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mOffset);
MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) {
MP3LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
if (!aTime.ToMicroseconds()) {
return FastSeek(aTime);
@ -236,27 +249,26 @@ MP3TrackDemuxer::ScanUntil(TimeUnit aTime) {
MediaByteRange nextRange = FindNextFrame();
while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
nextRange = FindNextFrame();
MP3DEMUXER_LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mOffset, Duration(mFrameIndex + 1));
MP3LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mOffset, Duration(mFrameIndex + 1));
}
MP3DEMUXER_LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
mOffset);
MP3LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
return Duration(mFrameIndex);
}
RefPtr<MP3TrackDemuxer::SamplesPromise>
MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
MP3DEMUXER_LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d "
"mSamplesPerSecond=%d mChannels=%d",
aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
mSamplesPerFrame, mSamplesPerSecond, mChannels);
MP3LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d "
"mSamplesPerSecond=%d mChannels=%d",
aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
mSamplesPerFrame, mSamplesPerSecond, mChannels);
if (!aNumSamples) {
return SamplesPromise::CreateAndReject(
@ -274,12 +286,13 @@ MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
frames->mSamples.AppendElement(frame);
}
MP3DEMUXER_LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64
" mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64
" mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
"mChannels=%d",
frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames,
mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
MP3LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64
" mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64
" mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
"mChannels=%d",
frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames,
mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond,
mChannels);
if (frames->mSamples.IsEmpty()) {
return SamplesPromise::CreateAndReject(
@ -290,17 +303,9 @@ MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
void
MP3TrackDemuxer::Reset() {
MP3DEMUXER_LOG("Reset()");
mOffset = 0;
mFirstFrameOffset = 0;
mNumParsedFrames = 0;
mFrameIndex = 0;
mTotalFrameLen = 0;
mSamplesPerFrame = 0;
mSamplesPerSecond = 0;
mChannels = 0;
MP3LOG("Reset()");
FastSeek(TimeUnit());
mParser.Reset();
}
@ -338,10 +343,13 @@ MP3TrackDemuxer::Duration() const {
return TimeUnit::FromMicroseconds(-1);
}
const int64_t streamLen = StreamLength();
// Assume we know the exact number of frames from the VBR header.
int64_t numFrames = mParser.VBRInfo().NumFrames();
if (numFrames < 0) {
int64_t numFrames = 0;
const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
if (numAudioFrames) {
// VBR headers don't include the VBR header frame.
numFrames = numAudioFrames.value() + 1;
} else {
const int64_t streamLen = StreamLength();
if (streamLen < 0) {
// Unknown length, we can't estimate duration.
return TimeUnit::FromMicroseconds(-1);
@ -366,11 +374,11 @@ MP3TrackDemuxer::FindNextFrame() {
static const int BUFFER_SIZE = 4096;
static const int MAX_SKIPPED_BYTES = 10 * BUFFER_SIZE;
MP3DEMUXER_LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
mSamplesPerSecond, mChannels);
MP3LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
mSamplesPerFrame, mSamplesPerSecond, mChannels);
uint8_t buffer[BUFFER_SIZE];
int32_t read = 0;
@ -383,6 +391,7 @@ MP3TrackDemuxer::FindNextFrame() {
if ((!mParser.FirstFrame().Length() &&
mOffset - mParser.ID3Header().Size() > MAX_SKIPPED_BYTES) ||
(read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) {
MP3LOG("FindNext() EOS or exceeded MAX_SKIPPED_BYTES without a frame");
// This is not a valid MPEG audio stream or we've reached EOS, give up.
break;
}
@ -404,18 +413,17 @@ MP3TrackDemuxer::FindNextFrame() {
}
if (!foundFrame || !mParser.CurrentFrame().Length()) {
MP3DEMUXER_LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
foundFrame, mParser.CurrentFrame().Length());
MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
foundFrame, mParser.CurrentFrame().Length());
return { 0, 0 };
}
MP3DEMUXER_LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " frameHeaderOffset=%d"
" mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
"mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
mTotalFrameLen, mSamplesPerFrame,
mSamplesPerSecond, mChannels);
MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " frameHeaderOffset=%d"
" mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d"
" mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
return { frameHeaderOffset, frameHeaderOffset + mParser.CurrentFrame().Length() };
}
@ -430,18 +438,19 @@ MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) {
UpdateState(aRange);
MP3DEMUXER_LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
mSamplesPerSecond, mChannels);
MP3LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
mSamplesPerFrame, mSamplesPerSecond, mChannels);
return true;
}
already_AddRefed<MediaRawData>
MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
MP3DEMUXER_LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})");
MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})",
aRange.mStart, aRange.Length());
if (!aRange.Length()) {
return nullptr;
}
@ -451,14 +460,14 @@ MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
if (!frameWriter->SetSize(aRange.Length())) {
MP3DEMUXER_LOG("GetNext() Exit failed to allocated media buffer");
MP3LOG("GetNext() Exit failed to allocated media buffer");
return nullptr;
}
const uint32_t read = Read(frameWriter->Data(), frame->mOffset, frame->Size());
if (read != aRange.Length()) {
MP3DEMUXER_LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size());
MP3LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size());
return nullptr;
}
@ -481,15 +490,43 @@ MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
mFirstFrameOffset = frame->mOffset;
}
MP3DEMUXER_LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
mSamplesPerSecond, mChannels);
MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
mSamplesPerFrame, mSamplesPerSecond, mChannels);
return frame.forget();
}
int64_t
MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
int64_t frameIndex = 0;
const auto& vbr = mParser.VBRInfo();
if (vbr.NumBytes() && vbr.NumAudioFrames()) {
frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
vbr.NumBytes().value() * vbr.NumAudioFrames().value();
frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
} else if (AverageFrameLength() > 0) {
frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength();
}
MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
return std::max<int64_t>(0, frameIndex);
}
int64_t
MP3TrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const {
int64_t frameIndex = 0;
if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {
frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1;
}
MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(), frameIndex);
return std::max<int64_t>(0, frameIndex);
}
void
MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
// Prevent overflow.
@ -504,9 +541,13 @@ MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
mOffset = aRange.mEnd;
mTotalFrameLen += aRange.Length();
mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
mChannels = mParser.CurrentFrame().Header().Channels();
if (!mSamplesPerFrame) {
mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
mChannels = mParser.CurrentFrame().Header().Channels();
}
++mNumParsedFrames;
++mFrameIndex;
MOZ_ASSERT(mFrameIndex > 0);
@ -517,7 +558,7 @@ MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
int32_t
MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) {
MP3DEMUXER_LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
const int64_t streamLen = StreamLength();
if (mInfo && streamLen > 0) {
@ -526,7 +567,7 @@ MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) {
}
uint32_t read = 0;
MP3DEMUXER_LOGV("MP3TrackDemuxer::Read -> ReadAt(%d)", aSize);
MP3LOGV("MP3TrackDemuxer::Read -> ReadAt(%d)", aSize);
const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast<char*>(aBuffer),
static_cast<uint32_t>(aSize), &read);
NS_ENSURE_SUCCESS(rv, 0);
@ -535,10 +576,15 @@ MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) {
double
MP3TrackDemuxer::AverageFrameLength() const {
if (!mNumParsedFrames) {
return 0.0;
if (mNumParsedFrames) {
return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
}
return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
const auto& vbr = mParser.VBRInfo();
if (vbr.NumBytes() && vbr.NumAudioFrames()) {
return static_cast<double>(vbr.NumBytes().value()) /
(vbr.NumAudioFrames().value() + 1);
}
return 0.0;
}
// FrameParser
@ -558,7 +604,6 @@ FrameParser::FrameParser()
void
FrameParser::Reset() {
mID3Parser.Reset();
mFirstFrame.Reset();
mFrame.Reset();
}
@ -619,13 +664,13 @@ FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip) {
if (skipSize > aReader->Remaining()) {
// Skipping across the ID3v2 tag would take us past the end of the buffer, therefore we
// return immediately and let the calling function handle skipping the rest of the tag.
MP3DEMUXER_LOGV("ID3v2 tag detected, size=%d, "
"needing to skip %d bytes past the current buffer",
tagSize, skipSize - aReader->Remaining());
MP3LOGV("ID3v2 tag detected, size=%d,"
" needing to skip %d bytes past the current buffer",
tagSize, skipSize - aReader->Remaining());
*aBytesToSkip = skipSize - aReader->Remaining();
return false;
}
MP3DEMUXER_LOGV("ID3v2 tag detected, size=%d", tagSize);
MP3LOGV("ID3v2 tag detected, size=%d", tagSize);
aReader->Read(skipSize);
} else {
// No ID3v2 tag found, rewinding reader in order to search for a MPEG frame header.
@ -836,9 +881,13 @@ FrameParser::FrameHeader::Update(uint8_t c) {
// FrameParser::VBRHeader
namespace vbr_header {
static const char* TYPE_STR[3] = {"NONE", "XING", "VBRI"};
static const uint32_t TOC_SIZE = 100;
} // namespace vbr_header
FrameParser::VBRHeader::VBRHeader()
: mNumFrames(-1),
mType(NONE)
: mType(NONE)
{
}
@ -847,17 +896,51 @@ FrameParser::VBRHeader::Type() const {
return mType;
}
const Maybe<uint32_t>&
FrameParser::VBRHeader::NumAudioFrames() const {
return mNumAudioFrames;
}
const Maybe<uint32_t>&
FrameParser::VBRHeader::NumBytes() const {
return mNumBytes;
}
const Maybe<uint32_t>&
FrameParser::VBRHeader::Scale() const {
return mScale;
}
bool
FrameParser::VBRHeader::IsTOCPresent() const {
return mTOC.size() == vbr_header::TOC_SIZE;
}
int64_t
FrameParser::VBRHeader::NumFrames() const {
return mNumFrames;
FrameParser::VBRHeader::Offset(float aDurationFac) const {
if (!IsTOCPresent()) {
return -1;
}
// Constrain the duration percentage to [0, 99].
const float durationPer = 100.0f * std::min(0.99f, std::max(0.0f, aDurationFac));
const size_t fullPer = durationPer;
const float rest = durationPer - fullPer;
MOZ_ASSERT(fullPer < mTOC.size());
int64_t offset = mTOC.at(fullPer);
if (rest > 0.0 && fullPer + 1 < mTOC.size()) {
offset += rest * (mTOC.at(fullPer + 1) - offset);
}
return offset;
}
bool
FrameParser::VBRHeader::ParseXing(ByteReader* aReader) {
static const uint32_t TAG = BigEndian::readUint32("Xing");
static const uint32_t TAG2 = BigEndian::readUint32("Info");
static const uint32_t FRAME_COUNT_OFFSET = 8;
static const uint32_t FRAME_COUNT_SIZE = 4;
static const uint32_t XING_TAG = BigEndian::readUint32("Xing");
static const uint32_t INFO_TAG = BigEndian::readUint32("Info");
enum Flags {
NUM_FRAMES = 0x01,
@ -870,24 +953,44 @@ FrameParser::VBRHeader::ParseXing(ByteReader* aReader) {
const size_t prevReaderOffset = aReader->Offset();
// We have to search for the Xing header as its position can change.
while (aReader->Remaining() >= FRAME_COUNT_OFFSET + FRAME_COUNT_SIZE) {
if (aReader->PeekU32() != TAG && aReader->PeekU32() != TAG2) {
aReader->Read(1);
continue;
}
// Skip across the VBR header ID tag.
aReader->Read(sizeof(TAG));
const uint32_t flags = aReader->ReadU32();
if (flags & NUM_FRAMES) {
mNumFrames = aReader->ReadU32();
}
mType = XING;
aReader->Seek(prevReaderOffset);
return true;
while (aReader->CanRead32() &&
aReader->PeekU32() != XING_TAG && aReader->PeekU32() != INFO_TAG) {
aReader->Read(1);
}
if (aReader->CanRead32()) {
// Skip across the VBR header ID tag.
aReader->ReadU32();
mType = XING;
}
uint32_t flags = 0;
if (aReader->CanRead32()) {
flags = aReader->ReadU32();
}
if (flags & NUM_FRAMES && aReader->CanRead32()) {
mNumAudioFrames = Some(aReader->ReadU32());
}
if (flags & NUM_BYTES && aReader->CanRead32()) {
mNumBytes = Some(aReader->ReadU32());
}
if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) {
if (!mNumBytes) {
// We don't have the stream size to calculate offsets, skip the TOC.
aReader->Read(vbr_header::TOC_SIZE);
} else {
mTOC.clear();
mTOC.reserve(vbr_header::TOC_SIZE);
for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) {
mTOC.push_back(1.0f / 256.0f * aReader->ReadU8() * mNumBytes.value());
}
}
}
if (flags & VBR_SCALE && aReader->CanRead32()) {
mScale = Some(aReader->ReadU32());
}
aReader->Seek(prevReaderOffset);
return false;
return mType == XING;
}
bool
@ -908,7 +1011,7 @@ FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) {
aReader->Seek(prevReaderOffset + OFFSET);
if (aReader->ReadU32() == TAG) {
aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET);
mNumFrames = aReader->ReadU32();
mNumAudioFrames = Some(aReader->ReadU32());
mType = VBRI;
aReader->Seek(prevReaderOffset);
return true;
@ -920,7 +1023,14 @@ FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) {
bool
FrameParser::VBRHeader::Parse(ByteReader* aReader) {
return ParseVBRI(aReader) || ParseXing(aReader);
const bool rv = ParseVBRI(aReader) || ParseXing(aReader);
if (rv) {
MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s"
" NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%u",
vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0),
NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size());
}
return rv;
}
// FrameParser::Frame

View File

@ -6,6 +6,7 @@
#define MP3_DEMUXER_H_
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "MediaDataDemuxer.h"
#include "MediaResource.h"
#include "mp4_demuxer/ByteReader.h"
@ -205,8 +206,9 @@ public:
// this class to parse them and access this info.
class VBRHeader {
public:
// Synchronize with vbr_header TYPE_STR on change.
enum VBRHeaderType {
NONE,
NONE = 0,
XING,
VBRI
};
@ -217,8 +219,22 @@ public:
// Returns the parsed VBR header type, or NONE if no valid header found.
VBRHeaderType Type() const;
// Returns the total number of frames expected in the stream/file.
int64_t NumFrames() const;
// Returns the total number of audio frames (excluding the VBR header frame)
// expected in the stream/file.
const Maybe<uint32_t>& NumAudioFrames() const;
// Returns the expected size of the stream.
const Maybe<uint32_t>& NumBytes() const;
// Returns the VBR scale factor (0: best quality, 100: lowest quality).
const Maybe<uint32_t>& Scale() const;
// Returns true iff Xing/Info TOC (table of contents) is present.
bool IsTOCPresent() const;
// Returns the byte offset for the given duration percentage as a factor
// (0: begin, 1.0: end).
int64_t Offset(float aDurationFac) const;
// Parses contents of given ByteReader for a valid VBR header.
// The offset of the passed ByteReader needs to point to an MPEG frame begin,
@ -240,7 +256,16 @@ public:
bool ParseVBRI(mp4_demuxer::ByteReader* aReader);
// The total number of frames expected as parsed from a VBR header.
int64_t mNumFrames;
Maybe<uint32_t> mNumAudioFrames;
// The total number of bytes expected in the stream.
Maybe<uint32_t> mNumBytes;
// The VBR scale factor.
Maybe<uint32_t> mScale;
// The TOC table mapping duration percentage to byte offset.
std::vector<int64_t> mTOC;
// The detected VBR header type.
VBRHeaderType mType;
@ -369,10 +394,10 @@ private:
~MP3TrackDemuxer() {}
// Fast approximate seeking to given time.
media::TimeUnit FastSeek(media::TimeUnit aTime);
media::TimeUnit FastSeek(const media::TimeUnit& aTime);
// Seeks by scanning the stream up to the given time for more accurate results.
media::TimeUnit ScanUntil(media::TimeUnit aTime);
media::TimeUnit ScanUntil(const media::TimeUnit& aTime);
// Finds the next valid frame and returns its byte range.
MediaByteRange FindNextFrame();
@ -386,6 +411,12 @@ private:
// Updates post-read meta data.
void UpdateState(const MediaByteRange& aRange);
// Returns the frame index for the given offset.
int64_t FrameIndexFromOffset(int64_t aOffset) const;
// Returns the frame index for the given time.
int64_t FrameIndexFromTime(const media::TimeUnit& aTime) const;
// Reads aSize bytes into aBuffer from the source starting at aOffset.
// Returns the actual size read.
int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);

View File

@ -59,6 +59,11 @@ enum class ReadMetadataFailureReason : int8_t
// Unless otherwise specified, methods and fields of this class can only
// be accessed on the decode task queue.
class MediaDecoderReader {
friend class ReRequestVideoWithSkipTask;
friend class ReRequestAudioTask;
static const bool IsExclusive = true;
public:
enum NotDecodedReason {
END_OF_STREAM,
@ -67,16 +72,20 @@ public:
CANCELED
};
typedef MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, /* IsExclusive = */ true> MetadataPromise;
typedef MozPromise<RefPtr<MediaData>, NotDecodedReason, /* IsExclusive = */ true> AudioDataPromise;
typedef MozPromise<RefPtr<MediaData>, NotDecodedReason, /* IsExclusive = */ true> VideoDataPromise;
typedef MozPromise<int64_t, nsresult, /* IsExclusive = */ true> SeekPromise;
using MetadataPromise =
MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>;
using AudioDataPromise =
MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
using VideoDataPromise =
MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
using SeekPromise = MozPromise<int64_t, nsresult, IsExclusive>;
// Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
// But in the current architecture it's only ever used exclusively (by MDSM),
// so we mark it that way to verify our assumptions. If you have a use-case
// for multiple WaitForData consumers, feel free to flip the exclusivity here.
typedef MozPromise<MediaData::Type, WaitForDataRejectValue, /* IsExclusive = */ true> WaitForDataPromise;
using WaitForDataPromise =
MozPromise<MediaData::Type, WaitForDataRejectValue, IsExclusive>;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
@ -84,19 +93,13 @@ public:
// destroyed.
explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
// Does any spinup that needs to happen on this task queue. This runs on a
// different thread than Init, and there should not be ordering dependencies
// between the two (even though in practice, Init will always run first right
// now thanks to the tail dispatcher).
void InitializationTask();
// Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
// on failure.
virtual nsresult Init() { return NS_OK; }
// Release media resources they should be released in dormant state
// The reader can be made usable again by calling ReadMetadata().
virtual void ReleaseMediaResources() {};
virtual void ReleaseMediaResources() {}
// Breaks reference-counted cycles. Called during shutdown.
// WARNING: If you override this, you must call the base implementation
// in your override.
@ -144,14 +147,15 @@ public:
virtual RefPtr<VideoDataPromise>
RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
friend class ReRequestVideoWithSkipTask;
friend class ReRequestAudioTask;
// By default, the state machine polls the reader once per second when it's
// in buffering mode. Some readers support a promise-based mechanism by which
// they notify the state machine when the data arrives.
virtual bool IsWaitForDataSupported() { return false; }
virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType)
{
MOZ_CRASH();
}
// By default, the reader return the decoded data. Some readers support
// retuning demuxed data.
@ -161,30 +165,19 @@ public:
// upon calls to Request{Audio,Video}Data.
virtual void SetDemuxOnly(bool /*aDemuxedOnly*/) {}
virtual bool HasAudio() = 0;
virtual bool HasVideo() = 0;
// The default implementation of AsyncReadMetadata is implemented in terms of
// synchronous ReadMetadata() calls. Implementations may also
// override AsyncReadMetadata to create a more proper async implementation.
virtual RefPtr<MetadataPromise> AsyncReadMetadata();
// Read header data for all bitstreams in the file. Fills aInfo with
// the data required to present the media, and optionally fills *aTags
// with tag metadata from the file.
// Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) { MOZ_CRASH(); }
// Fills aInfo with the latest cached data required to present the media,
// ReadUpdatedMetadata will always be called once ReadMetadata has succeeded.
virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
virtual void ReadUpdatedMetadata(MediaInfo* aInfo) {}
// Moves the decode head to aTime microseconds. aEndTime denotes the end
// time of the media in usecs. This is only needed for OggReader, and should
// probably be removed somehow.
virtual RefPtr<SeekPromise>
Seek(int64_t aTime, int64_t aEndTime) = 0;
virtual RefPtr<SeekPromise> Seek(int64_t aTime, int64_t aEndTime) = 0;
// Called to move the reader into idle state. When the reader is
// created it is assumed to be active (i.e. not idle). When the media
@ -196,7 +189,7 @@ public:
// Note: DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek should
// activate the decoder if necessary. The state machine only needs to know
// when to call SetIdle().
virtual void SetIdle() { }
virtual void SetIdle() {}
#ifdef MOZ_EME
virtual void SetCDMProxy(CDMProxy* aProxy) {}
@ -210,27 +203,6 @@ public:
mIgnoreAudioOutputFormat = true;
}
// Populates aBuffered with the time ranges which are buffered. This may only
// be called on the decode task queue, and should only be used internally by
// UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
// else.
//
// This base implementation in MediaDecoderReader estimates the time ranges
// buffered by interpolating the cached byte ranges with the duration
// of the media. Reader subclasses should override this method if they
// can quickly calculate the buffered ranges more accurately.
//
// The primary advantage of this implementation in the reader base class
// is that it's a fast approximation, which does not perform any I/O.
//
// The OggReader relies on this base implementation not performing I/O,
// since in FirefoxOS we can't do I/O on the main thread, where this is
// called.
virtual media::TimeIntervals GetBuffered();
// Recomputes mBuffered.
virtual void UpdateBuffered();
// MediaSourceReader opts out of the start-time-guessing mechanism.
virtual bool ForceZeroStartTime() const { return false; }
@ -251,9 +223,26 @@ public:
virtual size_t SizeOfVideoQueueInFrames();
virtual size_t SizeOfAudioQueueInFrames();
protected:
friend class TrackBuffer;
virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) { }
// In situations where these notifications come from stochastic network
// activity, we can save significant recomputation by throttling the delivery
// of these updates to the reader implementation. We don't want to do this
// throttling when the update comes from MSE code, since that code needs the
// updates to be observable immediately, and is generally less
// trigger-happy with notifications anyway.
void DispatchNotifyDataArrived(uint32_t aLength,
int64_t aOffset,
bool aThrottleUpdates)
{
typedef media::Interval<int64_t> Interval;
RefPtr<nsRunnable> r = NS_NewRunnableMethodWithArg<Interval>(
this,
aThrottleUpdates ? &MediaDecoderReader::ThrottledNotifyDataArrived :
&MediaDecoderReader::NotifyDataArrived,
Interval(aOffset, aOffset + aLength));
OwnerThread()->Dispatch(
r.forget(), AbstractThread::DontAssertDispatchSuccess);
}
void NotifyDataArrived(const media::Interval<int64_t>& aInfo)
{
@ -263,41 +252,14 @@ protected:
UpdateBuffered();
}
// Invokes NotifyDataArrived while throttling the calls to occur at most every mThrottleDuration ms.
void ThrottledNotifyDataArrived(const media::Interval<int64_t>& aInterval);
void DoThrottledNotify();
public:
// In situations where these notifications come from stochastic network
// activity, we can save significant recomputation by throttling the delivery
// of these updates to the reader implementation. We don't want to do this
// throttling when the update comes from MSE code, since that code needs the
// updates to be observable immediately, and is generally less
// trigger-happy with notifications anyway.
void DispatchNotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates)
{
RefPtr<nsRunnable> r =
NS_NewRunnableMethodWithArg<media::Interval<int64_t>>(this, aThrottleUpdates ? &MediaDecoderReader::ThrottledNotifyDataArrived
: &MediaDecoderReader::NotifyDataArrived,
media::Interval<int64_t>(aOffset, aOffset + aLength));
OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
}
// Notify the reader that data from the resource was evicted (MediaSource only)
virtual void NotifyDataRemoved() {}
virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
virtual MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
// Returns a pointer to the decoder.
AbstractMediaDecoder* GetDecoder() {
return mDecoder;
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered()
{
return &mBuffered;
}
RefPtr<VideoDataPromise> DecodeToFirstVideoData();
MediaInfo GetMediaInfo() { return mInfo; }
// Indicates if the media is seekable.
// ReadMetada should be called before calling this method.
virtual bool IsMediaSeekable() = 0;
@ -316,7 +278,8 @@ public:
OwnerThread()->Dispatch(r.forget());
}
TaskQueue* OwnerThread() const {
TaskQueue* OwnerThread() const
{
return mTaskQueue;
}
@ -332,30 +295,41 @@ public:
virtual void DisableHardwareAcceleration() {}
TimedMetadataEventSource& TimedMetadataEvent() {
TimedMetadataEventSource& TimedMetadataEvent()
{
return mTimedMetadataEvent;
}
protected:
virtual ~MediaDecoderReader();
// Overrides of this function should decodes an unspecified amount of
// audio data, enqueuing the audio data in mAudioQueue. Returns true
// when there's more audio to decode, false if the audio is finished,
// end of file has been reached, or an un-recoverable read error has
// occured. This function blocks until the decode is complete.
virtual bool DecodeAudioData() {
return false;
// Populates aBuffered with the time ranges which are buffered. This may only
// be called on the decode task queue, and should only be used internally by
// UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
// else.
//
// This base implementation in MediaDecoderReader estimates the time ranges
// buffered by interpolating the cached byte ranges with the duration
// of the media. Reader subclasses should override this method if they
// can quickly calculate the buffered ranges more accurately.
//
// The primary advantage of this implementation in the reader base class
// is that it's a fast approximation, which does not perform any I/O.
//
// The OggReader relies on this base implementation not performing I/O,
// since in FirefoxOS we can't do I/O on the main thread, where this is
// called.
virtual media::TimeIntervals GetBuffered();
RefPtr<VideoDataPromise> DecodeToFirstVideoData();
bool HaveStartTime()
{
MOZ_ASSERT(OnTaskQueue());
return mStartTime.isSome();
}
// Overrides of this function should read and decodes one video frame.
// Packets with a timestamp less than aTimeThreshold will be decoded
// (unless they're not keyframes and aKeyframeSkip is true), but will
// not be added to the queue. This function blocks until the decode
// is complete.
virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) {
return false;
}
int64_t StartTime() { MOZ_ASSERT(HaveStartTime()); return mStartTime.ref(); }
// Queue of audio frames. This queue is threadsafe, and is accessed from
// the audio, decoder, state machine, and main threads.
@ -385,9 +359,6 @@ protected:
// Buffered range.
Canonical<media::TimeIntervals> mBuffered;
public:
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() { return &mBuffered; }
protected:
// Stores presentation info required for playback.
MediaInfo mInfo;
@ -417,8 +388,6 @@ protected:
// things such that all GetBuffered calls go through the MDSM, which would
// offset the range accordingly.
Maybe<int64_t> mStartTime;
bool HaveStartTime() { MOZ_ASSERT(OnTaskQueue()); return mStartTime.isSome(); }
int64_t StartTime() { MOZ_ASSERT(HaveStartTime()); return mStartTime.ref(); }
// This is a quick-and-dirty way for DecodeAudioData implementations to
// communicate the presence of a decoding error to RequestAudioData. We should
@ -431,6 +400,51 @@ protected:
TimedMetadataEventProducer mTimedMetadataEvent;
private:
// Does any spinup that needs to happen on this task queue. This runs on a
// different thread than Init, and there should not be ordering dependencies
// between the two (even though in practice, Init will always run first right
// now thanks to the tail dispatcher).
void InitializationTask();
// Read header data for all bitstreams in the file. Fills aInfo with
// the data required to present the media, and optionally fills *aTags
// with tag metadata from the file.
// Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
{
MOZ_CRASH();
}
// Recomputes mBuffered.
virtual void UpdateBuffered();
virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) {}
// Invokes NotifyDataArrived while throttling the calls to occur
// at most every mThrottleDuration ms.
void ThrottledNotifyDataArrived(const media::Interval<int64_t>& aInterval);
void DoThrottledNotify();
// Overrides of this function should decodes an unspecified amount of
// audio data, enqueuing the audio data in mAudioQueue. Returns true
// when there's more audio to decode, false if the audio is finished,
// end of file has been reached, or an un-recoverable read error has
// occured. This function blocks until the decode is complete.
virtual bool DecodeAudioData()
{
return false;
}
// Overrides of this function should read and decodes one video frame.
// Packets with a timestamp less than aTimeThreshold will be decoded
// (unless they're not keyframes and aKeyframeSkip is true), but will
// not be added to the queue. This function blocks until the decode
// is complete.
virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
{
return false;
}
// Promises used only for the base-class (sync->async adapter) implementation
// of Request{Audio,Video}Data.
MozPromiseHolder<AudioDataPromise> mBaseAudioPromise;

View File

@ -1544,11 +1544,8 @@ MediaFormatReader::NotifyDemuxer(uint32_t aLength, int64_t aOffset)
return;
}
if (aLength || aOffset) {
mDemuxer->NotifyDataArrived();
} else {
mDemuxer->NotifyDataRemoved();
}
mDemuxer->NotifyDataArrived();
if (!mInitDone) {
return;
}
@ -1571,14 +1568,6 @@ MediaFormatReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
NotifyDemuxer(aLength, aOffset);
}
void
MediaFormatReader::NotifyDataRemoved()
{
MOZ_ASSERT(OnTaskQueue());
NotifyDemuxer(0, 0);
}
bool
MediaFormatReader::ForceZeroStartTime() const
{

View File

@ -42,16 +42,6 @@ public:
RefPtr<AudioDataPromise> RequestAudioData() override;
bool HasVideo() override
{
return mVideo.mTrackDemuxer;
}
bool HasAudio() override
{
return mAudio.mTrackDemuxer;
}
RefPtr<MetadataPromise> AsyncReadMetadata() override;
void ReadUpdatedMetadata(MediaInfo* aInfo) override;
@ -66,9 +56,8 @@ public:
protected:
void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
public:
void NotifyDataRemoved() override;
public:
media::TimeIntervals GetBuffered() override;
virtual bool ForceZeroStartTime() const override;
@ -113,6 +102,9 @@ public:
#endif
private:
bool HasVideo() { return mVideo.mTrackDemuxer; }
bool HasAudio() { return mAudio.mTrackDemuxer; }
bool IsWaitingOnCDMResource();
bool InitDemuxer();

View File

@ -48,16 +48,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold);
virtual bool HasAudio()
{
return mHasAudio;
}
virtual bool HasVideo()
{
return mHasVideo;
}
virtual bool IsMediaSeekable()
{
// not used

View File

@ -303,21 +303,6 @@ AppleMP3Reader::DecodeVideoFrame(bool &aKeyframeSkip,
return false;
}
bool
AppleMP3Reader::HasAudio()
{
MOZ_ASSERT(OnTaskQueue());
return mStreamReady;
}
bool
AppleMP3Reader::HasVideo()
{
MOZ_ASSERT(OnTaskQueue());
return false;
}
bool
AppleMP3Reader::IsMediaSeekable()
{

View File

@ -28,9 +28,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
virtual bool HasAudio() override;
virtual bool HasVideo() override;
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;

View File

@ -341,20 +341,6 @@ DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip,
return false;
}
bool
DirectShowReader::HasAudio()
{
MOZ_ASSERT(OnTaskQueue());
return true;
}
bool
DirectShowReader::HasVideo()
{
MOZ_ASSERT(OnTaskQueue());
return false;
}
RefPtr<MediaDecoderReader::SeekPromise>
DirectShowReader::Seek(int64_t aTargetUs, int64_t aEndTime)
{

View File

@ -47,9 +47,6 @@ public:
bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
bool HasAudio() override;
bool HasVideo() override;
nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;

View File

@ -56,21 +56,13 @@ protected:
virtual void NotifyDataArrivedInternal(uint32_t aLength,
int64_t aOffset) override;
public:
virtual bool HasAudio() override {
return mInfo.HasAudio();
}
virtual bool HasVideo() override {
return mInfo.HasVideo();
}
layers::ImageContainer* GetImageContainer() { return mDecoder->GetImageContainer(); }
virtual bool IsMediaSeekable() override;
private:
bool HasAudio() { return mInfo.HasAudio(); }
bool HasVideo() { return mInfo.HasVideo(); }
void ReadAndPushData(guint aLength);
RefPtr<layers::PlanarYCbCrImage> GetImageFromBuffer(GstBuffer* aBuffer);
void CopyIntoImageBuffer(GstBuffer *aBuffer, GstBuffer** aOutBuffer, RefPtr<layers::PlanarYCbCrImage> &image);

View File

@ -224,10 +224,10 @@ TEST_F(MP3DemuxerTest, VBRHeader) {
if (target.mIsVBR) {
EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
// TODO: find reference number which accounts for trailing headers.
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, vbr.NumFrames());
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, vbr.NumAudioFrames().value());
} else {
EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
EXPECT_EQ(-1, vbr.NumFrames());
EXPECT_FALSE(vbr.NumAudioFrames());
}
}
}

View File

@ -60,15 +60,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
virtual bool HasAudio() override {
return (mVorbisState != 0 && mVorbisState->mActive) ||
(mOpusState != 0 && mOpusState->mActive);
}
virtual bool HasVideo() override {
return mTheoraState != 0 && mTheoraState->mActive;
}
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;
virtual RefPtr<SeekPromise>
@ -78,6 +69,15 @@ public:
virtual bool IsMediaSeekable() override;
private:
bool HasAudio() {
return (mVorbisState != 0 && mVorbisState->mActive) ||
(mOpusState != 0 && mOpusState->mActive);
}
bool HasVideo() {
return mTheoraState != 0 && mTheoraState->mActive;
}
// TODO: DEPRECATED. This uses synchronous decoding.
// Stores the presentation time of the first frame we'd be able to play if
// we started playback at the current position. Returns the first video

View File

@ -86,9 +86,6 @@ public:
// Disptach a DecodeAduioDataTask to decode video data.
virtual RefPtr<AudioDataPromise> RequestAudioData() override;
virtual bool HasAudio();
virtual bool HasVideo();
virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
// Moves the decode head to aTime microseconds. aStartTime and aEndTime
@ -181,6 +178,8 @@ protected:
MozPromiseHolder<MediaResourcePromise> mMediaResourcePromise;
private:
virtual bool HasAudio() override;
virtual bool HasVideo() override;
// An intermediary class that can be managed by android::sp<T>.
// Redirect codecReserved() and codecCanceled() to MediaCodecReader.

View File

@ -49,6 +49,10 @@ protected:
android::MediaStreamSource* mStreamSource;
// Get value from the preferece, if true, we stop the audio offload.
bool IsMonoAudioEnabled();
private:
virtual bool HasAudio() = 0;
virtual bool HasVideo() = 0;
};
} // namespace mozilla

View File

@ -85,16 +85,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold);
virtual bool HasAudio()
{
return mHasAudio;
}
virtual bool HasVideo()
{
return mHasVideo;
}
virtual void ReleaseMediaResources() override;
virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
@ -118,6 +108,9 @@ private:
class ProcessCachedDataTask;
class NotifyDataArrivedRunnable;
virtual bool HasAudio() override { return mHasAudio; }
virtual bool HasVideo() override { return mHasVideo; }
bool IsShutdown() {
MutexAutoLock lock(mShutdownMutex);
return mIsShutdown;

View File

@ -26,7 +26,7 @@ using namespace mozilla::widget::sdk;
namespace mozilla {
#define ENVOKE_CALLBACK(Func, ...) \
#define INVOKE_CALLBACK(Func, ...) \
if (mCallback) { \
mCallback->Func(__VA_ARGS__); \
} else { \
@ -192,7 +192,7 @@ public:
gfx::IntRect(0, 0,
mConfig.mDisplay.width,
mConfig.mDisplay.height));
ENVOKE_CALLBACK(Output, v);
INVOKE_CALLBACK(Output, v);
return NS_OK;
}
@ -263,7 +263,7 @@ public:
audio,
numChannels,
sampleRate);
ENVOKE_CALLBACK(Output, data);
INVOKE_CALLBACK(Output, data);
return NS_OK;
}
};
@ -382,7 +382,7 @@ nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
{
mDecoder = CreateDecoder(mMimeType);
if (!mDecoder) {
ENVOKE_CALLBACK(Error);
INVOKE_CALLBACK(Error);
return NS_ERROR_FAILURE;
}
@ -406,10 +406,10 @@ nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
if (NS_FAILED(res)) { \
NS_WARNING("exiting decoder loop due to exception"); \
if (mDraining) { \
ENVOKE_CALLBACK(DrainComplete); \
INVOKE_CALLBACK(DrainComplete); \
mDraining = false; \
} \
ENVOKE_CALLBACK(Error); \
INVOKE_CALLBACK(Error); \
break; \
}
@ -455,7 +455,7 @@ void MediaCodecDataDecoder::DecoderLoop()
while (!mStopping && !mDraining && !mFlushing && mQueue.empty()) {
if (mQueue.empty()) {
// We could be waiting here forever if we don't signal that we need more input
ENVOKE_CALLBACK(InputExhausted);
INVOKE_CALLBACK(InputExhausted);
}
lock.Wait();
}
@ -473,14 +473,14 @@ void MediaCodecDataDecoder::DecoderLoop()
continue;
}
if (mDraining && !sample && !waitingEOF) {
draining = true;
}
// We're not stopping or draining, so try to get a sample
if (!mQueue.empty()) {
sample = mQueue.front();
}
if (mDraining && !sample && !waitingEOF) {
draining = true;
}
}
if (draining && !waitingEOF) {
@ -552,7 +552,7 @@ void MediaCodecDataDecoder::DecoderLoop()
HANDLE_DECODER_ERROR();
} else if (outputStatus < 0) {
NS_WARNING("unknown error from decoder!");
ENVOKE_CALLBACK(Error);
INVOKE_CALLBACK(Error);
// Don't break here just in case it's recoverable. If it's not, others stuff will fail later and
// we'll bail out.
@ -572,7 +572,7 @@ void MediaCodecDataDecoder::DecoderLoop()
mMonitor.Notify();
mMonitor.Unlock();
ENVOKE_CALLBACK(DrainComplete);
INVOKE_CALLBACK(DrainComplete);
}
mDecoder->ReleaseOutputBuffer(outputStatus, false);

View File

@ -26,16 +26,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
virtual bool HasAudio() override
{
return false;
}
virtual bool HasVideo() override
{
return true;
}
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;
virtual RefPtr<SeekPromise>

View File

@ -26,16 +26,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
virtual bool HasAudio() override
{
return true;
}
virtual bool HasVideo() override
{
return false;
}
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;
virtual RefPtr<SeekPromise>

View File

@ -69,6 +69,14 @@ protected:
~WebMReader();
public:
// Returns a pointer to the decoder.
AbstractMediaDecoder* GetDecoder()
{
return mDecoder;
}
MediaInfo GetMediaInfo() { return mInfo; }
virtual RefPtr<ShutdownPromise> Shutdown() override;
virtual nsresult Init() override;
virtual nsresult ResetDecode() override;
@ -77,18 +85,6 @@ public:
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold) override;
virtual bool HasAudio() override
{
MOZ_ASSERT(OnTaskQueue());
return mHasAudio;
}
virtual bool HasVideo() override
{
MOZ_ASSERT(OnTaskQueue());
return mHasVideo;
}
virtual RefPtr<MetadataPromise> AsyncReadMetadata() override;
virtual RefPtr<SeekPromise>

View File

@ -0,0 +1 @@
<applet xmlns="http://www.w3.org/1999/xhtml" />

View File

@ -13,3 +13,4 @@ skip-if(browserIsRemote||!haveTestPlugin||http.platform!="X11") load 598862.html
# SkiaGL is causing a compositor hang here, disable temporarily while that gets resolved in bug 908363
skip-if(Android) load 626602-1.html
load 752340.html
load 843086.xhtml

View File

@ -546,6 +546,13 @@ PresentationControllingInfo::GetAddress()
return NS_OK;
}
NS_IMETHODIMP
PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
{
MOZ_ASSERT(false, "Should not receive ICE candidates.");
return NS_ERROR_FAILURE;
}
nsresult
PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
{
@ -607,7 +614,7 @@ PresentationControllingInfo::NotifyClosed(nsresult aReason)
// subsequent |Shutdown| calls.
SetControlChannel(nullptr);
if (NS_WARN_IF(NS_FAILED(aReason))) {
if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
// The presentation session instance may already exist.
// Change the state to TERMINATED since it never succeeds.
SetState(nsIPresentationSessionListener::STATE_TERMINATED);
@ -851,6 +858,13 @@ PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescrip
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
{
MOZ_ASSERT(false, "Should not receive ICE candidates.");
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
PresentationPresentingInfo::NotifyOpened()
{

View File

@ -32,7 +32,7 @@ interface nsIPresentationChannelDescription: nsISupports
/*
* The callbacks for events on control channel.
*/
[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)]
[scriptable, uuid(96dd548f-7d0f-43c1-b1ad-28e666cf1e82)]
interface nsIPresentationControlChannelListener: nsISupports
{
/*
@ -47,6 +47,12 @@ interface nsIPresentationControlChannelListener: nsISupports
*/
void onAnswer(in nsIPresentationChannelDescription answer);
/*
* Callback for receiving ICE candidate from remote endpoint.
* @param answer The received answer.
*/
void onIceCandidate(in DOMString candidate);
/*
* The callback for notifying channel opened.
*/
@ -63,7 +69,7 @@ interface nsIPresentationControlChannelListener: nsISupports
* The control channel for establishing RTCPeerConnection for a presentation
* session. SDP Offer/Answer will be exchanged through this interface.
*/
[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
interface nsIPresentationControlChannel: nsISupports
{
// The listener for handling events of this control channel.
@ -71,26 +77,28 @@ interface nsIPresentationControlChannel: nsISupports
attribute nsIPresentationControlChannelListener listener;
/*
* Send offer to remote endpiont. |onOffer| should be invoked
* on remote endpoint.
* Send offer to remote endpoint. |onOffer| should be invoked on remote
* endpoint.
* @param offer The offer to send.
* @throws NS_ERROR_FAILURE on failure
*/
void sendOffer(in nsIPresentationChannelDescription offer);
/*
* Send answer to remote endpiont. |onAnswer| should
* be invoked on remote endpoint.
* Send answer to remote endpoint. |onAnswer| should be invoked on remote
* endpoint.
* @param answer The answer to send.
* @throws NS_ERROR_FAILURE on failure
*/
void sendAnswer(in nsIPresentationChannelDescription answer);
/*
* Notify the app-to-app connection is fully established. (Only used at the
* receiver side.)
* Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked
* on remote endpoint.
* @param candidate The candidate to send
* @throws NS_ERROR_FAILURE on failure
*/
void sendReceiverReady();
void sendIceCandidate(in DOMString candidate);
/*
* Close the transport channel.

View File

@ -374,6 +374,14 @@ TCPControlChannel.prototype = {
this._sendMessage("answer", msg);
},
sendIceCandidate: function(aCandidate) {
let msg = {
type: "requestSession:IceCandidate",
presentationId: this.presentationId,
iceCandidate: aCandidate,
};
this._sendMessage("iceCandidate", msg);
},
// may throw an exception
_send: function(aMsg) {
DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2));
@ -438,7 +446,7 @@ TCPControlChannel.prototype = {
onStopRequest: function(aRequest, aContext, aStatus) {
DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
+ " with role: " + this._direction);
this.close();
this.close(Cr.NS_OK);
this._notifyClosed(aStatus);
},
@ -496,6 +504,14 @@ TCPControlChannel.prototype = {
this._onAnswer(aMsg.answer);
break;
}
case "requestSession:IceCandidate": {
this._listener.onIceCandidate(aMsg.iceCandidate);
break;
}
case "requestSession:CloseReason": {
this._pendingCloseReason = aMsg.reason;
break;
}
}
},
@ -573,7 +589,7 @@ TCPControlChannel.prototype = {
_notifyOpened: function() {
this._connected = true;
this._pendingClose = false;
this._pendingCloseReason = null;
this._pendingCloseReason = Cr.NS_OK;
if (!this._listener) {
this._pendingOpen = true;
@ -591,10 +607,15 @@ TCPControlChannel.prototype = {
this._pendingOffer = null;
this._pendingAnswer = null;
// Remote endpoint closes the control channel with abnormal reason.
if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
aReason = this._pendingCloseReason;
}
if (!this._listener) {
this._pendingClose = true;
this._pendingCloseReason = aReason;
return;
this._pendingClose = true;
this._pendingCloseReason = aReason;
return;
}
DEBUG && log("TCPControlChannel - notify closed with role: "
@ -602,9 +623,21 @@ TCPControlChannel.prototype = {
this._listener.notifyClosed(aReason);
},
close: function() {
DEBUG && log("TCPControlChannel - close");
close: function(aReason) {
DEBUG && log("TCPControlChannel - close with reason: " + aReason);
if (this._connected) {
// default reason is NS_OK
if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
let msg = {
type: "requestSession:CloseReason",
presentationId: this.presentationId,
reason: aReason,
};
this._sendMessage("close", msg);
this._pendingCloseReason = aReason;
}
this._transport.setEventSink(null, null);
this._pump = null;

View File

@ -115,6 +115,49 @@ function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit
});
}
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
});
request.start().then(
function(aConnection) {
ok(false, "|start| shouldn't succeed in this case.");
aReject();
},
function(aError) {
is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
aResolve();
}
);
});
}
function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@ -169,6 +212,60 @@ function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportRead
});
}
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
});
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
});
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
});
request.start().then(
function(aConnection) {
ok(false, "|start| shouldn't succeed in this case.");
aReject();
},
function(aError) {
is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
aResolve();
}
);
});
}
function testStartConnectionUnexpectedDataTransportClose() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@ -240,7 +337,9 @@ function runTests() {
then(setup).
then(testStartConnectionCancelPrompt).
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit).
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady).
then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady).
then(testStartConnectionUnexpectedDataTransportClose).
then(teardown);
}

View File

@ -44,6 +44,9 @@ TestDescription.prototype = {
const CONTROLLER_CONTROL_CHANNEL_PORT = 36777;
const PRESENTER_CONTROL_CHANNEL_PORT = 36888;
var CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_OK;
var candidate;
// presenter's presentation channel description
const OFFER_ADDRESS = '192.168.123.123';
const OFFER_PORT = 123;
@ -98,13 +101,23 @@ function testPresentationServer() {
onAnswer: function(aAnswer) {
Assert.ok(false, 'get answer');
},
onIceCandidate: function(aCandidate) {
Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel');
let recvCandidate = JSON.parse(aCandidate);
for (let key in recvCandidate) {
if (typeof(recvCandidate[key]) !== "function") {
Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match.");
}
}
controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON);
},
notifyOpened: function() {
Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
this.status = 'opened';
},
notifyClosed: function(aReason) {
Assert.equal(this.status, 'onOffer', '3. controllerControlChannel: closed');
Assert.equal(aReason, Cr.NS_OK, 'presenterControlChannel notify closed NS_OK');
Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
this.status = 'closed';
yayFuncs.controllerControlChannelClose();
},
@ -132,15 +145,22 @@ function testPresentationServer() {
Assert.ok(false, 'get offer');
},
onAnswer: function(aAnswer) {
Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, close channel');
Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate');
let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription);
Assert.strictEqual(answer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data,
ANSWER_ADDRESS,
'expected answer address array');
Assert.equal(answer.tcpPort, ANSWER_PORT, 'expected answer port');
presenterControlChannel.close(Cr.NS_OK);
candidate = {
candidate: "1 1 UDP 1 127.0.0.1 34567 type host",
sdpMid: "helloworld",
sdpMLineIndex: 1
};
presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
},
onIceCandidate: function(aCandidate) {
Assert.ok(false, 'get ICE candidate');
},
notifyOpened: function() {
Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
@ -155,7 +175,7 @@ function testPresentationServer() {
},
notifyClosed: function(aReason) {
this.status = 'closed';
Assert.equal(aReason, Cr.NS_OK, '3. presenterControlChannel notify closed NS_OK');
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
yayFuncs.presenterControlChannelClose();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
@ -198,8 +218,15 @@ function shutdown()
tps.close();
}
// Test manually close control channel with NS_ERROR_FAILURE
function changeCloseReason() {
CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE;
run_next_test();
}
add_test(loopOfferAnser);
add_test(setOffline);
add_test(changeCloseReason);
add_test(oneMoreLoop);
add_test(shutdown);

View File

@ -638,7 +638,7 @@ this.PushServiceWebSocket = {
try {
// Grab a wakelock before we open the socket to ensure we don't go to
// sleep before connection the is opened.
this._ws.asyncOpen(uri, uri.spec, this._wsListener, null);
this._ws.asyncOpen(uri, uri.spec, 0, this._wsListener, null);
this._acquireWakeLock();
this._currentState = STATE_WAITING_FOR_WS_START;
} catch(e) {

View File

@ -279,7 +279,7 @@ MockWebSocket.prototype = {
return this._originalURI;
},
asyncOpen(uri, origin, listener, context) {
asyncOpen(uri, origin, windowId, listener, context) {
this._listener = listener;
this._context = context;
waterfall(() => this._listener.onStart(this._context));

View File

@ -847,7 +847,7 @@ this.PushService = {
try {
// Grab a wakelock before we open the socket to ensure we don't go to sleep
// before connection the is opened.
this._ws.asyncOpen(uri, serverURL, this._wsListener, null);
this._ws.asyncOpen(uri, serverURL, 0, this._wsListener, null);
this._acquireWakeLock();
this._currentState = STATE_WAITING_FOR_WS_START;
} catch(e) {

View File

@ -0,0 +1,15 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
[Constructor,
JSImplementation="@mozilla.org/dom/browser-element-proxy;1",
NavigatorProperty="mozBrowserElementProxy",
Pref="dom.mozBrowserFramesEnabled",
CheckAnyPermissions="browser:embedded-system-app"]
interface BrowserElementProxy : EventTarget {
};
BrowserElementProxy implements BrowserElementCommon;
BrowserElementProxy implements BrowserElementPrivileged;

View File

@ -56,16 +56,14 @@ interface IDBIndex {
partial interface IDBIndex {
[Throws]
IDBRequest mozGetAll (optional any key, optional unsigned long limit);
IDBRequest mozGetAll (optional any key, [EnforceRange] optional unsigned long limit);
[Throws]
IDBRequest mozGetAllKeys (optional any key, optional unsigned long limit);
IDBRequest mozGetAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
[Throws,
Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
IDBRequest getAll (optional any key, optional unsigned long limit);
[Throws]
IDBRequest getAll (optional any key, [EnforceRange] optional unsigned long limit);
[Throws,
Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
IDBRequest getAllKeys (optional any key, optional unsigned long limit);
[Throws]
IDBRequest getAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
};

View File

@ -63,17 +63,14 @@ interface IDBObjectStore {
partial interface IDBObjectStore {
// Success fires IDBTransactionEvent, result == array of values for given keys
[Throws]
IDBRequest mozGetAll (optional any key, optional unsigned long limit);
IDBRequest mozGetAll (optional any key, [EnforceRange] optional unsigned long limit);
[Throws,
Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
IDBRequest getAll (optional any key, optional unsigned long limit);
[Throws]
IDBRequest getAll (optional any key, [EnforceRange] optional unsigned long limit);
[Throws,
Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
IDBRequest getAllKeys (optional any key, optional unsigned long limit);
[Throws]
IDBRequest getAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
[Throws,
Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
[Throws]
IDBRequest openKeyCursor (optional any range, optional IDBCursorDirection direction = "next");
};

View File

@ -59,6 +59,7 @@ WEBIDL_FILES = [
'BrowserElement.webidl',
'BrowserElementAudioChannel.webidl',
'BrowserElementDictionaries.webidl',
'BrowserElementProxy.webidl',
'Cache.webidl',
'CacheStorage.webidl',
'CallsList.webidl',

View File

@ -976,7 +976,13 @@ private:
principal = parentWorker->GetPrincipal();
}
aLoadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(channelPrincipal));
// We don't mute the main worker script becase we've already done
// same-origin checks on them so we should be able to see their errors.
// Note that for data: url, where we allow it through the same-origin check
// but then give it a different origin.
aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript()
? false
: !principal->Subsumes(channelPrincipal));
// Make sure we're not seeing the result of a 404 or something by checking
// the 'requestSucceeded' attribute on the http channel.

View File

@ -101,16 +101,6 @@ ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo));
}
void
ServiceWorker::QueueStateChangeEvent(ServiceWorkerState aState)
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethodWithArg<ServiceWorkerState>(this,
&ServiceWorker::DispatchStateChange,
aState);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
}
} // namespace workers
} // namespace dom
} // namespace mozilla

View File

@ -55,13 +55,9 @@ public:
void
DispatchStateChange(ServiceWorkerState aState)
{
SetState(aState);
DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("statechange"));
}
void
QueueStateChangeEvent(ServiceWorkerState aState);
#ifdef XP_WIN
#undef PostMessage
#endif

View File

@ -442,9 +442,15 @@ FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
return;
}
if (!mPromise) {
mPromise = &aArg;
// 4.5.3.2 If the respond-with entered flag is set, then:
// Throw an "InvalidStateError" exception.
// Here we use |mPromise != nullptr| as respond-with enter flag
if (mPromise) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mPromise = &aArg;
RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
StopImmediatePropagation();
mWaitToRespond = true;

View File

@ -372,6 +372,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& a
: mControlledDocumentsCounter(0)
, mScope(aScope)
, mPrincipal(aPrincipal)
, mLastUpdateCheckTime(0)
, mPendingUninstall(false)
{ }
@ -1182,7 +1183,7 @@ private:
}
nsresult rv =
serviceWorkerScriptCache::Compare(mRegistration->mPrincipal, cacheName,
serviceWorkerScriptCache::Compare(mRegistration, mRegistration->mPrincipal, cacheName,
NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec),
this, mLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -1905,15 +1906,18 @@ ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
return NS_ERROR_FAILURE;
}
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetRegistration(serviceWorker->GetPrincipal(), aScope);
if (optional_argc == 2) {
nsTArray<uint8_t> data;
if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data));
return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data), registration);
} else {
MOZ_ASSERT(optional_argc == 0);
return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing());
return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing(), registration);
}
#endif // MOZ_SIMPLEPUSH
}
@ -2382,6 +2386,33 @@ ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess)
swm->StoreRegistration(mPrincipal, this);
}
void
ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
{
MOZ_ASSERT(NS_IsMainThread());
mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
}
bool
ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
{
MOZ_ASSERT(NS_IsMainThread());
// For testing.
if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
return true;
}
const uint64_t kSecondsPerDay = 86400;
const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
if ((mLastUpdateCheckTime != 0) &&
(now - mLastUpdateCheckTime > kSecondsPerDay)) {
return true;
}
return false;
}
void
ServiceWorkerManager::LoadRegistration(
const ServiceWorkerRegistrationData& aRegistration)
@ -4181,6 +4212,42 @@ ServiceWorkerInfo::RemoveWorker(ServiceWorker* aWorker)
mInstances.RemoveElement(aWorker);
}
namespace {
class ChangeStateUpdater final : public nsRunnable
{
public:
ChangeStateUpdater(const nsTArray<ServiceWorker*>& aInstances,
ServiceWorkerState aState)
: mState(aState)
{
for (size_t i = 0; i < aInstances.Length(); ++i) {
mInstances.AppendElement(aInstances[i]);
}
}
NS_IMETHODIMP Run()
{
// We need to update the state of all instances atomically before notifying
// them to make sure that the observed state for all instances inside
// statechange event handlers is correct.
for (size_t i = 0; i < mInstances.Length(); ++i) {
mInstances[i]->SetState(mState);
}
for (size_t i = 0; i < mInstances.Length(); ++i) {
mInstances[i]->DispatchStateChange(mState);
}
return NS_OK;
}
private:
nsAutoTArray<RefPtr<ServiceWorker>, 1> mInstances;
ServiceWorkerState mState;
};
}
void
ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
{
@ -4197,9 +4264,8 @@ ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
#endif
mState = aState;
for (uint32_t i = 0; i < mInstances.Length(); ++i) {
mInstances[i]->QueueStateChangeEvent(mState);
}
nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r.forget())));
}
ServiceWorkerInfo::ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,

View File

@ -73,6 +73,8 @@ public:
RefPtr<ServiceWorkerInfo> mWaitingWorker;
RefPtr<ServiceWorkerInfo> mInstallingWorker;
uint64_t mLastUpdateCheckTime;
// When unregister() is called on a registration, it is not immediately
// removed since documents may be controlled. It is marked as
// pendingUninstall and when all controlling documents go away, removed.
@ -132,6 +134,11 @@ public:
void
FinishActivate(bool aSuccess);
void
RefreshLastUpdateCheckTime();
bool
IsLastUpdateCheckTimeOverOneDay() const;
};
class ServiceWorkerUpdateFinishCallback

View File

@ -5,8 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerPrivate.h"
#include "ServiceWorkerManager.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "mozilla/dom/FetchUtil.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -191,6 +193,51 @@ public:
NS_IMPL_ISUPPORTS0(KeepAliveHandler)
class SoftUpdateRequest : public nsRunnable
{
protected:
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
public:
explicit SoftUpdateRequest(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
: mRegistration(aRegistration)
{
MOZ_ASSERT(aRegistration);
}
NS_IMETHOD Run()
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
OriginAttributes attrs =
mozilla::BasePrincipal::Cast(mRegistration->mPrincipal)->OriginAttributesRef();
swm->PropagateSoftUpdate(attrs,
NS_ConvertUTF8toUTF16(mRegistration->mScope));
return NS_OK;
}
};
class CheckLastUpdateTimeRequest final : public SoftUpdateRequest
{
public:
explicit CheckLastUpdateTimeRequest(
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
: SoftUpdateRequest(aRegistration)
{}
NS_IMETHOD Run()
{
AssertIsOnMainThread();
if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
SoftUpdateRequest::Run();
}
return NS_OK;
}
};
class ExtendableEventWorkerRunnable : public WorkerRunnable
{
protected:
@ -248,6 +295,33 @@ public:
}
};
// Handle functional event
// 9.9.7 If the time difference in seconds calculated by the current time minus
// registration's last update check time is greater than 86400, invoke Soft Update
// algorithm.
class ExtendableFunctionalEventWorkerRunnable : public ExtendableEventWorkerRunnable
{
protected:
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
public:
ExtendableFunctionalEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
KeepAliveToken* aKeepAliveToken,
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
: ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
, mRegistration(aRegistration)
{
MOZ_ASSERT(aRegistration);
}
void
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
{
nsCOMPtr<nsIRunnable> runnable = new CheckLastUpdateTimeRequest(mRegistration);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
}
};
/*
* Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
* since it fires the event. This is ok since there can't be nested
@ -343,7 +417,7 @@ public:
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
xpcReport->Init(report.report(), report.message(),
/* aIsChrome = */ false, /* aWindowID = */ 0);
/* aIsChrome = */ false, workerPrivate->WindowID());
RefPtr<AsyncErrorReporter> aer = new AsyncErrorReporter(xpcReport);
NS_DispatchToMainThread(aer);
@ -415,15 +489,17 @@ ServiceWorkerPrivate::SendLifeCycleEvent(const nsAString& aEventType,
#ifndef MOZ_SIMPLEPUSH
namespace {
class SendPushEventRunnable final : public ExtendableEventWorkerRunnable
class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
{
Maybe<nsTArray<uint8_t>> mData;
public:
SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
KeepAliveToken* aKeepAliveToken,
const Maybe<nsTArray<uint8_t>>& aData)
: ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
const Maybe<nsTArray<uint8_t>>& aData,
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
: ExtendableFunctionalEventWorkerRunnable(
aWorkerPrivate, aKeepAliveToken, aRegistration)
, mData(aData)
{
AssertIsOnMainThread();
@ -504,7 +580,8 @@ public:
#endif // !MOZ_SIMPLEPUSH
nsresult
ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData)
ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
ServiceWorkerRegistrationInfo* aRegistration)
{
#ifdef MOZ_SIMPLEPUSH
return NS_ERROR_NOT_AVAILABLE;
@ -513,9 +590,14 @@ ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData)
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(mKeepAliveToken);
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
mKeepAliveToken,
aData);
aData,
regInfo);
AutoJSAPI jsapi;
jsapi.Init();
if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
@ -827,7 +909,7 @@ namespace {
// Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
// while handling the fetch event, though that's very unlikely.
class FetchEventRunnable : public ExtendableEventWorkerRunnable
class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable
, public nsIHttpHeaderVisitor {
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
const nsCString mScriptSpec;
@ -851,9 +933,11 @@ public:
// CSP checks might require the worker script spec
// later on.
const nsACString& aScriptSpec,
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
bool aIsReload)
: ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
: ExtendableFunctionalEventWorkerRunnable(
aWorkerPrivate, aKeepAliveToken, aRegistration)
, mInterceptedChannel(aChannel)
, mScriptSpec(aScriptSpec)
, mClientInfo(Move(aClientInfo))
@ -947,8 +1031,17 @@ public:
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
if (uploadChannel) {
MOZ_ASSERT(!mUploadStream);
rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
bool bodyHasHeaders = false;
rv = uploadChannel->GetUploadStreamHasHeaders(&bodyHasHeaders);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> uploadStream;
rv = uploadChannel->CloneUploadStream(getter_AddRefs(uploadStream));
NS_ENSURE_SUCCESS(rv, rv);
if (bodyHasHeaders) {
HandleBodyWithHeaders(uploadStream);
} else {
mUploadStream = uploadStream;
}
}
} else {
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
@ -1090,8 +1183,61 @@ private:
new KeepAliveHandler(mKeepAliveToken);
respondWithPromise->AppendNativeHandler(keepAliveHandler);
}
// 9.8.22 If request is a non-subresource request, then: Invoke Soft Update algorithm
if (internalReq->IsNavigationRequest()) {
nsCOMPtr<nsIRunnable> runnable= new SoftUpdateRequest(mRegistration);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
}
return true;
}
nsresult
HandleBodyWithHeaders(nsIInputStream* aUploadStream)
{
// We are dealing with an nsMIMEInputStream which uses string input streams
// under the hood, so all of the data is available synchronously.
bool nonBlocking = false;
nsresult rv = aUploadStream->IsNonBlocking(&nonBlocking);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(!nonBlocking)) {
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoCString body;
rv = NS_ConsumeStream(aUploadStream, UINT32_MAX, body);
NS_ENSURE_SUCCESS(rv, rv);
// Extract the headers in the beginning of the buffer
nsAutoCString::const_iterator begin, end;
body.BeginReading(begin);
body.EndReading(end);
const nsAutoCString::const_iterator body_end = end;
nsAutoCString headerName, headerValue;
bool emptyHeader = false;
while (FetchUtil::ExtractHeader(begin, end, headerName,
headerValue, &emptyHeader) &&
!emptyHeader) {
mHeaderNames.AppendElement(headerName);
mHeaderValues.AppendElement(headerValue);
headerName.Truncate();
headerValue.Truncate();
}
// Replace the upload stream with one only containing the body text.
nsCOMPtr<nsIStringInputStream> strStream =
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Skip past the "\r\n" that separates the headers and the body.
++begin;
++begin;
body.Assign(Substring(begin, body_end));
rv = strStream->SetData(body.BeginReading(), body.Length());
NS_ENSURE_SUCCESS(rv, rv);
mUploadStream = strStream;
return NS_OK;
}
};
NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
@ -1119,9 +1265,19 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
return NS_ERROR_FAILURE;
}
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
RefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(mInfo->GetPrincipal(), mInfo->Scope());
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(registration, false));
RefPtr<FetchEventRunnable> r =
new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle,
mInfo->ScriptSpec(), Move(aClientInfo), aIsReload);
mInfo->ScriptSpec(), regInfo,
Move(aClientInfo), aIsReload);
rv = r->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;

View File

@ -84,7 +84,8 @@ public:
nsIRunnable* aLoadFailure);
nsresult
SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData);
SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
ServiceWorkerRegistrationInfo* aRegistration);
nsresult
SendPushSubscriptionChangeEvent();

View File

@ -13,6 +13,7 @@
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsICacheInfoChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIStreamLoader.h"
#include "nsIThreadRetargetableRequest.h"
@ -93,72 +94,7 @@ public:
}
nsresult
Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
{
MOZ_ASSERT(aPrincipal);
AssertIsOnMainThread();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsILoadGroup> loadGroup;
rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Note that because there is no "serviceworker" RequestContext type, we can
// use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
// worker.
rv = NS_NewChannel(getter_AddRefs(mChannel),
uri, aPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
loadGroup,
nullptr, // aCallbacks
nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsLoadFlags flags;
rv = mChannel->GetLoadFlags(&flags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
flags |= nsIRequest::LOAD_BYPASS_CACHE;
rv = mChannel->SetLoadFlags(flags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
// Spec says no redirects allowed for SW scripts.
httpChannel->SetRedirectionLimit(0);
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
NS_LITERAL_CSTRING("script"),
/* merge */ false);
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mChannel->AsyncOpen2(loader);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup);
void
Abort()
@ -296,14 +232,17 @@ class CompareManager final : public PromiseNativeHandler
public:
NS_DECL_ISUPPORTS
explicit CompareManager(CompareCallback* aCallback)
: mCallback(aCallback)
explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
CompareCallback* aCallback)
: mRegistration(aRegistration)
, mCallback(aCallback)
, mState(WaitingForOpen)
, mNetworkFinished(false)
, mCacheFinished(false)
, mInCache(false)
{
AssertIsOnMainThread();
MOZ_ASSERT(aRegistration);
}
nsresult
@ -359,6 +298,13 @@ public:
mMaxScope = aMaxScope;
}
already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration()
{
RefPtr<ServiceWorkerRegistrationInfo> copy = mRegistration.get();
return copy.forget();
}
void
NetworkFinished(nsresult aStatus)
{
@ -621,6 +567,7 @@ private:
cachePromise->AppendNativeHandler(this);
}
RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
RefPtr<CompareCallback> mCallback;
JS::PersistentRooted<JSObject*> mSandbox;
RefPtr<CacheStorage> mCacheStorage;
@ -650,6 +597,69 @@ private:
NS_IMPL_ISUPPORTS0(CompareManager)
nsresult
CompareNetwork::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
{
MOZ_ASSERT(aPrincipal);
AssertIsOnMainThread();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsILoadGroup> loadGroup;
rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsLoadFlags flags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
RefPtr<ServiceWorkerRegistrationInfo> registration =
mManager->GetRegistration();
if (registration->IsLastUpdateCheckTimeOverOneDay()) {
flags |= nsIRequest::LOAD_BYPASS_CACHE;
}
// Note that because there is no "serviceworker" RequestContext type, we can
// use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
// worker.
rv = NS_NewChannel(getter_AddRefs(mChannel),
uri, aPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
loadGroup,
nullptr, // aCallbacks
flags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
// Spec says no redirects allowed for SW scripts.
httpChannel->SetRedirectionLimit(0);
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
NS_LITERAL_CSTRING("script"),
/* merge */ false);
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mChannel->AsyncOpen2(loader);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
@ -732,6 +742,20 @@ CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext
mManager->SetMaxScope(maxScope);
bool isFromCache = false;
nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
if (cacheChannel) {
cacheChannel->IsFromCache(&isFromCache);
}
// [9.2 Update]4.13, If response's cache state is not "local",
// set registration's last update check time to the current time
if (!isFromCache) {
RefPtr<ServiceWorkerRegistrationInfo> registration =
mManager->GetRegistration();
registration->RefreshLastUpdateCheckTime();
}
nsAutoCString mimeType;
rv = httpChannel->GetContentType(mimeType);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -772,7 +796,7 @@ CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext
if (NS_WARN_IF(!scheme.LowerCaseEqualsLiteral("app"))) {
mManager->NetworkFinished(NS_ERROR_FAILURE);
return NS_OK;
return NS_OK;
}
}
@ -1018,16 +1042,18 @@ GenerateCacheName(nsAString& aName)
}
nsresult
Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
Compare(ServiceWorkerRegistrationInfo* aRegistration,
nsIPrincipal* aPrincipal, const nsAString& aCacheName,
const nsAString& aURL, CompareCallback* aCallback,
nsILoadGroup* aLoadGroup)
{
AssertIsOnMainThread();
MOZ_ASSERT(aRegistration);
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(!aURL.IsEmpty());
MOZ_ASSERT(aCallback);
RefPtr<CompareManager> cm = new CompareManager(aCallback);
RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {

View File

@ -44,7 +44,8 @@ public:
};
nsresult
Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
Compare(ServiceWorkerRegistrationInfo* aRegistration,
nsIPrincipal* aPrincipal, const nsAString& aCacheName,
const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
} // namespace serviceWorkerScriptCache

View File

@ -220,8 +220,6 @@ skip-if = release_build
[test_origin_after_redirect_cached.html]
[test_origin_after_redirect_to_https.html]
[test_origin_after_redirect_to_https_cached.html]
[test_periodic_https_update.html]
[test_periodic_update.html]
[test_post_message.html]
[test_post_message_advanced.html]
[test_post_message_source.html]

View File

@ -5,6 +5,7 @@
#include "nsPlaintextEditor.h"
#include "gfxFontUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Selection.h"
@ -600,7 +601,8 @@ nsPlaintextEditor::ExtendSelectionForDelete(Selection* aSelection,
break;
case ePrevious: {
// Only extend the selection where the selection is after a UTF-16
// surrogate pair. For other cases we don't want to do that, in order
// surrogate pair or a variation selector.
// For other cases we don't want to do that, in order
// to make sure that pressing backspace will only delete the last
// typed character.
nsCOMPtr<nsIDOMNode> node;
@ -616,9 +618,11 @@ nsPlaintextEditor::ExtendSelectionForDelete(Selection* aSelection,
result = charData->GetData(data);
NS_ENSURE_SUCCESS(result, result);
if (offset > 1 &&
NS_IS_LOW_SURROGATE(data[offset - 1]) &&
NS_IS_HIGH_SURROGATE(data[offset - 2])) {
if ((offset > 1 &&
NS_IS_LOW_SURROGATE(data[offset - 1]) &&
NS_IS_HIGH_SURROGATE(data[offset - 2])) ||
(offset > 0 &&
gfxFontUtils::IsVarSelector(data[offset - 1]))) {
result = selCont->CharacterExtendForBackspace();
}
}

View File

@ -168,3 +168,4 @@ skip-if = toolkit == 'android'
[test_bug1186799.html]
[test_bug1181130-1.html]
[test_bug1181130-2.html]
[test_backspace_vs.html]

View File

@ -0,0 +1,130 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1216427
-->
<head>
<title>Test for Bug 1216427</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1216427">Mozilla Bug 1216427</a>
<p id="display"></p>
<div id="content">
<div id="edit1" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
<div id="edit2" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
<div id="edit3" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
<div id="edit4" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
<div id="edit5" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
<div id="edit6" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
<!-- The Regional Indicator combinations here were supported by Apple Color Emoji
even prior to the major extension of coverage in the 10.10.5 timeframe. -->
<div id="edit7" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
<div id="edit8" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
<div id="edit9" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
<div id="edit10" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
<div id="edit11" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
<div id="edit1b" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
<div id="edit2b" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
<div id="edit3b" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
<div id="edit4b" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
<div id="edit5b" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
<div id="edit6b" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
<div id="edit7b" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
<div id="edit8b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
<div id="edit9b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
<div id="edit10b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
<div id="edit11b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 1216427 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(runTest);
function test(edit, bsCount) {
edit.focus();
var sel = window.getSelection();
sel.collapse(edit.childNodes[0], edit.textContent.length - 1);
for (i = 0; i < bsCount; ++i) {
synthesizeKey("VK_BACK_SPACE", {});
}
is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
}
function testWithMove(edit, offset, bsCount) {
edit.focus();
var sel = window.getSelection();
sel.collapse(edit.childNodes[0], 0);
var i;
for (i = 0; i < offset; ++i) {
synthesizeKey("VK_RIGHT", {});
synthesizeKey("VK_LEFT", {});
synthesizeKey("VK_RIGHT", {});
}
for (i = 0; i < bsCount; ++i) {
synthesizeKey("VK_BACK_SPACE", {});
}
is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
}
function runTest() {
/* test backspace-deletion of the middle character(s) */
test(document.getElementById("edit1"), 1);
test(document.getElementById("edit2"), 1);
test(document.getElementById("edit3"), 1);
test(document.getElementById("edit4"), 1);
test(document.getElementById("edit5"), 1);
test(document.getElementById("edit6"), 1);
/*
* Tests with Regional Indicator flags: these behave differently depending
* whether an emoji font is present, as ligated flags are edited as single
* characters whereas non-ligated RI characters act individually.
*
* For now, only rely on such an emoji font on OS X 10.7+. (Note that the
* Segoe UI Emoji font on Win8.1 and Win10 does not implement Regional
* Indicator flags.)
*
* Once the Firefox Emoji font is ready, we can load that via @font-face
* and expect these tests to work across all platforms.
*/
hasEmojiFont =
(navigator.platform.indexOf("Mac") == 0 &&
/10\.([7-9]|[1-9][0-9])/.test(navigator.oscpu));
if (hasEmojiFont) {
test(document.getElementById("edit7"), 1);
test(document.getElementById("edit8"), 2);
test(document.getElementById("edit9"), 3);
test(document.getElementById("edit10"), 4);
test(document.getElementById("edit11"), 5);
}
/* extra tests with the use of RIGHT and LEFT to get to the right place */
testWithMove(document.getElementById("edit1b"), 2, 1);
testWithMove(document.getElementById("edit2b"), 2, 1);
testWithMove(document.getElementById("edit3b"), 2, 1);
testWithMove(document.getElementById("edit4b"), 2, 1);
testWithMove(document.getElementById("edit5b"), 2, 1);
testWithMove(document.getElementById("edit6b"), 2, 1);
if (hasEmojiFont) {
testWithMove(document.getElementById("edit7b"), 2, 1);
testWithMove(document.getElementById("edit8b"), 3, 2);
testWithMove(document.getElementById("edit9b"), 4, 3);
testWithMove(document.getElementById("edit10b"), 5, 4);
testWithMove(document.getElementById("edit11b"), 6, 5);
}
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -84,7 +84,7 @@ _hb_ft_font_create (FT_Face ft_face, bool unref)
ft_font->ft_face = ft_face;
ft_font->unref = unref;
ft_font->load_flags = FT_LOAD_DEFAULT;
ft_font->load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
return ft_font;
}

View File

@ -59,7 +59,7 @@ struct hb_ot_face_metrics_accelerator_t
this->blob = OT::Sanitizer<OT::_mtx>::sanitize (face->reference_table (_mtx_tag));
if (unlikely (!this->num_advances ||
2 * (this->num_advances + this->num_metrics) < hb_blob_get_length (this->blob)))
2 * (this->num_advances + this->num_metrics) > hb_blob_get_length (this->blob)))
{
this->num_metrics = this->num_advances = 0;
hb_blob_destroy (this->blob);

View File

@ -38,9 +38,9 @@ HB_BEGIN_DECLS
#define HB_VERSION_MAJOR 1
#define HB_VERSION_MINOR 0
#define HB_VERSION_MICRO 5
#define HB_VERSION_MICRO 6
#define HB_VERSION_STRING "1.0.5"
#define HB_VERSION_STRING "1.0.6"
#define HB_VERSION_ATLEAST(major,minor,micro) \
((major)*10000+(minor)*100+(micro) <= \

View File

@ -62,9 +62,6 @@ protected:
public:
// Mark user classes that are considered flawless.
template<typename U>
friend class RefPtr;
template<class U>
friend class ::mozilla::StaticRefPtr;

View File

@ -92,8 +92,9 @@ public:
virtual void EndFrame() override;
virtual void EndFrameForExternalComposition(const gfx::Matrix& aTransform) override
{
// XXX See Bug 1215364
NS_WARNING("BasicCOmpositor::EndFrameForExternalComposition - not implemented!");
MOZ_ASSERT(!mTarget);
MOZ_ASSERT(!mDrawTarget);
MOZ_ASSERT(!mRenderTarget);
}
virtual bool SupportsPartialTextureUpdate() override { return true; }

View File

@ -1505,12 +1505,7 @@ CompositorOGL::GetReleaseFence()
void
CompositorOGL::EndFrameForExternalComposition(const gfx::Matrix& aTransform)
{
// This lets us reftest and screenshot content rendered externally
if (mTarget) {
MakeCurrent();
CopyToTarget(mTarget, mTargetBounds.TopLeft(), aTransform);
mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
}
MOZ_ASSERT(!mTarget);
if (mTexturePool) {
mTexturePool->EndFrame();
}

View File

@ -573,9 +573,6 @@ gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
// mark all the rest as cluster-continuations
while (aString < iter) {
*glyphs = extendCluster;
if (NS_IS_LOW_SURROGATE(*aString)) {
glyphs->SetIsLowSurrogate();
}
glyphs++;
aString++;
}

View File

@ -741,8 +741,7 @@ public:
FLAG_CHAR_IS_TAB = 0x08,
FLAG_CHAR_IS_NEWLINE = 0x10,
FLAG_CHAR_IS_LOW_SURROGATE = 0x20,
CHAR_IDENTITY_FLAGS_MASK = 0x38,
CHAR_IDENTITY_FLAGS_MASK = 0x18,
GLYPH_COUNT_MASK = 0x00FFFF00U,
GLYPH_COUNT_SHIFT = 8
@ -793,9 +792,6 @@ public:
bool CharIsNewline() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
}
bool CharIsLowSurrogate() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0;
}
uint32_t CharIdentityFlags() const {
return IsSimpleGlyph() ? 0 : (mValue & CHAR_IDENTITY_FLAGS_MASK);
@ -870,10 +866,6 @@ public:
NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
mValue |= FLAG_CHAR_IS_NEWLINE;
}
void SetIsLowSurrogate() {
NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
mValue |= FLAG_CHAR_IS_LOW_SURROGATE;
}
private:
uint32_t mValue;
@ -908,11 +900,6 @@ public:
GetCharacterGlyphs()[aIndex].SetIsSpace();
}
void SetIsLowSurrogate(uint32_t aIndex) {
SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
GetCharacterGlyphs()[aIndex].SetIsLowSurrogate();
}
bool HasDetailedGlyphs() const {
return mDetailedGlyphs != nullptr;
}

View File

@ -903,6 +903,16 @@ public:
(ch >= kUnicodeVS17 && ch <= kUnicodeVS256);
}
enum {
kUnicodeRegionalIndicatorA = 0x1F1E6,
kUnicodeRegionalIndicatorZ = 0x1F1FF
};
static inline bool IsRegionalIndicator(uint32_t aCh) {
return aCh >= kUnicodeRegionalIndicatorA &&
aCh <= kUnicodeRegionalIndicatorZ;
}
static inline bool IsInvalid(uint32_t ch) {
return (ch == 0xFFFD);
}

View File

@ -1060,9 +1060,11 @@ gfxPlatform::ComputeTileSize()
if (gfxPrefs::LayersTilesAdjust()) {
gfx::IntSize screenSize = GetScreenSize();
if (screenSize.width > 0) {
// FIXME: we should probably make sure this is within the max texture size,
// but I think everything should at least support 1024
w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256);
// For the time being tiles larger than 512 probably do not make much
// sense. This is due to e.g. increased rasterisation time outweighing
// the decreased composition time, or large increases in memory usage
// for screens slightly wider than a higher power of two.
w = h = screenSize.width >= 512 ? 512 : 256;
}
#ifdef MOZ_WIDGET_GONK

View File

@ -128,10 +128,6 @@ public:
NS_ASSERTION(aPos < GetLength(), "aPos out of range");
return mCharacterGlyphs[aPos].CharIsNewline();
}
bool CharIsLowSurrogate(uint32_t aPos) const {
NS_ASSERTION(aPos < GetLength(), "aPos out of range");
return mCharacterGlyphs[aPos].CharIsLowSurrogate();
}
// All uint32_t aStart, uint32_t aLength ranges below are restricted to
// grapheme cluster boundaries! All offsets are in terms of the string
@ -535,10 +531,6 @@ public:
}
g->SetIsNewline();
}
void SetIsLowSurrogate(uint32_t aIndex) {
SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
mCharacterGlyphs[aIndex].SetIsLowSurrogate();
}
/**
* Prefetch all the glyph extents needed to ensure that Measure calls

View File

@ -296,9 +296,9 @@ ClippedImage::GetFrameInternal(const nsIntSize& aSize,
// Cache the resulting surface.
mCachedSurface =
new ClippedImageCachedSurface(target->Snapshot(), aSize, aSVGContext,
frameToDraw, aFlags,
drawTileCallback->GetDrawResult());
MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext,
frameToDraw, aFlags,
drawTileCallback->GetDrawResult());
}
MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");

View File

@ -10,6 +10,7 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace image {
@ -83,7 +84,7 @@ private:
uint32_t aFlags);
// If we are forced to draw a temporary surface, we cache it here.
nsAutoPtr<ClippedImageCachedSurface> mCachedSurface;
UniquePtr<ClippedImageCachedSurface> mCachedSurface;
nsIntRect mClip; // The region to clip to.
Maybe<bool> mShouldClip; // Memoized ShouldClip() if present.

View File

@ -9,7 +9,6 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Monitor.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsIThreadPool.h"

View File

@ -14,7 +14,6 @@
#include "nsComponentManagerUtils.h"
#include "nsError.h"
#include "Decoder.h"
#include "nsAutoPtr.h"
#include "prenv.h"
#include "prsystem.h"
#include "ImageContainer.h"

Some files were not shown because too many files have changed in this diff Show More