зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2ginbound, a=merge
--HG-- extra : commitid : 4RZDM8UPfkA
This commit is contained in:
Коммит
b91440b5e1
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -26,3 +26,4 @@ run-if = os == 'linux'
|
|||
[test_bug1008126.html]
|
||||
run-if = os == 'linux'
|
||||
[test_sandboxed_blob_uri.html]
|
||||
[test_websocket_frame.html]
|
||||
|
|
|
@ -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>
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
|
@ -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}
|
|
@ -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);
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -26,6 +26,8 @@ XPIDL_MODULE = 'browser-element'
|
|||
EXTRA_COMPONENTS += [
|
||||
'BrowserElementParent.js',
|
||||
'BrowserElementParent.manifest',
|
||||
'BrowserElementProxy.js',
|
||||
'BrowserElementProxy.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<applet xmlns="http://www.w3.org/1999/xhtml" />
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
};
|
||||
|
|
|
@ -59,6 +59,7 @@ WEBIDL_FILES = [
|
|||
'BrowserElement.webidl',
|
||||
'BrowserElementAudioChannel.webidl',
|
||||
'BrowserElementDictionaries.webidl',
|
||||
'BrowserElementProxy.webidl',
|
||||
'Cache.webidl',
|
||||
'CacheStorage.webidl',
|
||||
'CallsList.webidl',
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,3 +168,4 @@ skip-if = toolkit == 'android'
|
|||
[test_bug1186799.html]
|
||||
[test_bug1181130-1.html]
|
||||
[test_bug1181130-2.html]
|
||||
[test_backspace_vs.html]
|
||||
|
|
|
@ -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☺️b</div><!-- BMP symbol with VS16 -->
|
||||
<div id="edit2" contenteditable="true">a🌐︎b</div><!-- plane 1 symbol with VS15 -->
|
||||
<div id="edit3" contenteditable="true">a㐂󠄀b</div><!-- BMP ideograph with VS17 -->
|
||||
<div id="edit4" contenteditable="true">a𠀀󠄁b</div><!-- SMP ideograph with VS18 -->
|
||||
<div id="edit5" contenteditable="true">a☺︁︂︃b</div><!-- BMP symbol with extra VSes -->
|
||||
<div id="edit6" contenteditable="true">a𠀀󠄀󠄁󠄂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🇨🇳b</div><!-- Regional Indicator flag: CN -->
|
||||
<div id="edit8" contenteditable="true">a🇨🇳🇩🇪b</div><!-- two RI flags: CN, DE -->
|
||||
<div id="edit9" contenteditable="true">a🇨🇳🇩🇪🇪🇸b</div><!-- three RI flags: CN, DE, ES -->
|
||||
<div id="edit10" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷b</div><!-- four RI flags: CN, DE, ES, FR -->
|
||||
<div id="edit11" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷🇬🇧b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
|
||||
|
||||
<div id="edit1b" contenteditable="true">a☺️b</div><!-- BMP symbol with VS16 -->
|
||||
<div id="edit2b" contenteditable="true">a🌐︎b</div><!-- plane 1 symbol with VS15 -->
|
||||
<div id="edit3b" contenteditable="true">a㐂󠄀b</div><!-- BMP ideograph with VS17 -->
|
||||
<div id="edit4b" contenteditable="true">a𠀀󠄁b</div><!-- SMP ideograph with VS18 -->
|
||||
<div id="edit5b" contenteditable="true">a☺︁︂︃b</div><!-- BMP symbol with extra VSes -->
|
||||
<div id="edit6b" contenteditable="true">a𠀀󠄀󠄁󠄂b</div><!-- SMP symbol with extra VSes -->
|
||||
<div id="edit7b" contenteditable="true">a🇨🇳b</div><!-- Regional Indicator flag: CN -->
|
||||
<div id="edit8b" contenteditable="true">a🇨🇳🇩🇪b</div><!-- two RI flags: CN, DE -->
|
||||
<div id="edit9b" contenteditable="true">a🇨🇳🇩🇪🇪🇸b</div><!-- three RI flags: CN, DE, ES -->
|
||||
<div id="edit10b" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷b</div><!-- four RI flags: CN, DE, ES, FR -->
|
||||
<div id="edit11b" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷🇬🇧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>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) <= \
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIThreadPool.h"
|
||||
|
|
|
@ -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"
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче