merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-07-17 10:08:08 +02:00
Родитель a61e84c9f3 34ed117ba5
Коммит a0992595d6
222 изменённых файлов: 5802 добавлений и 1354 удалений

Просмотреть файл

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1267887 - Build skew after updating rust mp4parse
Bug 1286754 - Update the version of rust used on Windows

Просмотреть файл

@ -86,10 +86,10 @@ xpcAccessible::IntlGeneric()
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
}
inline Accessible*
inline AccessibleOrProxy
xpcAccessibleHyperLink::Intl()
{
return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
}
inline Accessible*

Просмотреть файл

@ -6,6 +6,7 @@
#include "Accessible-inl.h"
#include "xpcAccessibleDocument.h"
#include "nsNetUtil.h"
using namespace mozilla::a11y;
@ -15,10 +16,20 @@ xpcAccessibleHyperLink::GetStartIndex(int32_t* aStartIndex)
NS_ENSURE_ARG_POINTER(aStartIndex);
*aStartIndex = 0;
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
*aStartIndex = Intl()->StartOffset();
if (Intl().IsAccessible()) {
*aStartIndex = Intl().AsAccessible()->StartOffset();
} else {
bool isIndexValid = false;
uint32_t startOffset = Intl().AsProxy()->StartOffset(&isIndexValid);
if (!isIndexValid)
return NS_ERROR_FAILURE;
*aStartIndex = startOffset;
}
return NS_OK;
}
@ -28,10 +39,20 @@ xpcAccessibleHyperLink::GetEndIndex(int32_t* aEndIndex)
NS_ENSURE_ARG_POINTER(aEndIndex);
*aEndIndex = 0;
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
*aEndIndex = Intl()->EndOffset();
if (Intl().IsAccessible()) {
*aEndIndex = Intl().AsAccessible()->EndOffset();
} else {
bool isIndexValid = false;
uint32_t endOffset = Intl().AsProxy()->EndOffset(&isIndexValid);
if (!isIndexValid)
return NS_ERROR_FAILURE;
*aEndIndex = endOffset;
}
return NS_OK;
}
@ -41,10 +62,20 @@ xpcAccessibleHyperLink::GetAnchorCount(int32_t* aAnchorCount)
NS_ENSURE_ARG_POINTER(aAnchorCount);
*aAnchorCount = 0;
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
*aAnchorCount = Intl()->AnchorCount();
if (Intl().IsAccessible()) {
*aAnchorCount = Intl().AsAccessible()->AnchorCount();
} else {
bool isCountValid = false;
uint32_t anchorCount = Intl().AsProxy()->AnchorCount(&isCountValid);
if (!isCountValid)
return NS_ERROR_FAILURE;
*aAnchorCount = anchorCount;
}
return NS_OK;
}
@ -53,13 +84,31 @@ xpcAccessibleHyperLink::GetURI(int32_t aIndex, nsIURI** aURI)
{
NS_ENSURE_ARG_POINTER(aURI);
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
if (aIndex < 0 || aIndex >= static_cast<int32_t>(Intl()->AnchorCount()))
if (aIndex < 0)
return NS_ERROR_INVALID_ARG;
RefPtr<nsIURI>(Intl()->AnchorURIAt(aIndex)).forget(aURI);
if (Intl().IsAccessible()) {
if (aIndex >= static_cast<int32_t>(Intl().AsAccessible()->AnchorCount()))
return NS_ERROR_INVALID_ARG;
RefPtr<nsIURI>(Intl().AsAccessible()->AnchorURIAt(aIndex)).forget(aURI);
} else {
nsCString spec;
bool isURIValid = false;
Intl().AsProxy()->AnchorURIAt(aIndex, spec, &isURIValid);
if (!isURIValid)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
NS_ENSURE_SUCCESS(rv, rv);
uri.forget(aURI);
}
return NS_OK;
}
@ -70,13 +119,21 @@ xpcAccessibleHyperLink::GetAnchor(int32_t aIndex, nsIAccessible** aAccessible)
NS_ENSURE_ARG_POINTER(aAccessible);
*aAccessible = nullptr;
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
if (aIndex < 0 || aIndex >= static_cast<int32_t>(Intl()->AnchorCount()))
if (aIndex < 0)
return NS_ERROR_INVALID_ARG;
NS_IF_ADDREF(*aAccessible = ToXPC(Intl()->AnchorAt(aIndex)));
if (Intl().IsAccessible()) {
if (aIndex >= static_cast<int32_t>(Intl().AsAccessible()->AnchorCount()))
return NS_ERROR_INVALID_ARG;
NS_IF_ADDREF(*aAccessible = ToXPC(Intl().AsAccessible()->AnchorAt(aIndex)));
} else {
NS_IF_ADDREF(*aAccessible = ToXPC(Intl().AsProxy()->AnchorAt(aIndex)));
}
return NS_OK;
}
@ -86,9 +143,14 @@ xpcAccessibleHyperLink::GetValid(bool* aValid)
NS_ENSURE_ARG_POINTER(aValid);
*aValid = false;
if (!Intl())
if (Intl().IsNull())
return NS_ERROR_FAILURE;
*aValid = Intl()->IsLinkValid();
if (Intl().IsAccessible()) {
*aValid = Intl().AsAccessible()->IsLinkValid();
} else {
*aValid = Intl().AsProxy()->IsLinkValid();
}
return NS_OK;
}

Просмотреть файл

@ -39,7 +39,7 @@ private:
xpcAccessibleHyperLink(const xpcAccessibleHyperLink&) = delete;
xpcAccessibleHyperLink& operator =(const xpcAccessibleHyperLink&) = delete;
Accessible* Intl();
AccessibleOrProxy Intl();
};
} // namespace a11y

Просмотреть файл

@ -13,7 +13,8 @@
HAS_MISC_RULE = True
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini',
'source/test/leak/jetpack-package.ini']
JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
addons = [

Просмотреть файл

@ -8,6 +8,7 @@ module.metadata = {
};
var { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core");
const { Cu } = require("chrome");
// This module provides set of high order function for working with event
// streams (streams in a NodeJS style that dispatch data, end and error
@ -26,7 +27,7 @@ var refs = (function() {
})();
function transform(input, f) {
let output = {};
let output = new Output();
// Since event listeners don't prevent `input` to be GC-ed we wanna presrve
// it until `output` can be GC-ed. There for we add implicit reference which
@ -64,7 +65,7 @@ exports.map = map;
// into single event stream. Like flatten but time based rather than order
// based.
function merge(inputs) {
let output = {};
let output = new Output();
let open = 1;
let state = [];
output.state = state;
@ -107,13 +108,18 @@ exports.pipe = pipe;
// Shim signal APIs so other modules can be used as is.
const receive = (input, message) => {
if (input[receive])
input[receive](input, message);
else
emit(input, "data", message);
// Ideally our input will extend Input and already provide a weak value
// getter. If not, opportunistically shim the weak value getter on
// other types passed as the input.
if (!("value" in input)) {
Object.defineProperty(input, "value", WeakValueGetterSetter);
}
input.value = message;
};
receive.toString = () => "@@receive";
@ -151,7 +157,7 @@ const lift = (step, ...inputs) => {
let args = null;
let opened = inputs.length;
let started = false;
const output = {};
const output = new Output();
const init = () => {
args = [...inputs.map(input => input.value)];
output.value = step(...args);
@ -182,7 +188,8 @@ exports.lift = lift;
const merges = inputs => {
let opened = inputs.length;
let output = { value: inputs[0].value };
let output = new Output();
output.value = inputs[0].value;
inputs.forEach((input, index) => {
on(input, "data", data => receive(output, data));
on(input, "end", () => {
@ -225,12 +232,46 @@ Input.end = input => {
};
Input.prototype[end] = Input.end;
// The event channel system caches the last event seen as input.value.
// Unfortunately, if the last event is a DOM object this is a great way
// leak windows. Mitigate this by storing input.value using a weak
// reference. This allows the system to work for normal event processing
// while also allowing the objects to be reclaimed. It means, however,
// input.value cannot be accessed long after the event was dispatched.
const WeakValueGetterSetter = {
get: function() {
return this._weakValue ? this._weakValue.get() : this._simpleValue
},
set: function(v) {
if (v && typeof v === "object") {
this._weakValue = Cu.getWeakReference(v)
this._simpleValue = undefined;
return;
}
this._simpleValue = v;
this._weakValue = undefined;
},
}
Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter);
exports.Input = Input;
// Define an Output type with a weak value getter for the transformation
// functions that produce new channels.
function Output() { }
Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter);
exports.Output = Output;
const $source = "@@source";
const $outputs = "@@outputs";
exports.outputs = $outputs;
// NOTE: Passing DOM objects through a Reactor can cause them to leak
// when they get cached in this.value. We cannot use a weak reference
// in this case because the Reactor design expects to always have both the
// past and present value. If we allow past values to be collected the
// system breaks.
function Reactor(options={}) {
const {onStep, onStart, onEnd} = options;
if (onStep)

Просмотреть файл

@ -7,22 +7,40 @@ module.metadata = {
"stability": "unstable"
};
const { Ci } = require("chrome");
const { Ci, Cu } = require("chrome");
const { observe } = require("../event/chrome");
const { open } = require("../event/dom");
const { windows } = require("../window/utils");
const { filter, merge, map, expand } = require("../event/utils");
function documentMatches(weakWindow, event) {
let window = weakWindow.get();
return window && event.target === window.document;
}
function makeStrictDocumentFilter(window) {
// Note: Do not define a closure within this function. Otherwise
// you may leak the window argument.
let weak = Cu.getWeakReference(window);
return documentMatches.bind(null, weak);
}
function toEventWithDefaultViewTarget({type, target}) {
return { type: type, target: target.defaultView }
}
// Function registers single shot event listeners for relevant window events
// that forward events to exported event stream.
function eventsFor(window) {
// NOTE: Do no use pass a closure from this function into a stream
// transform function. You will capture the window in the
// closure and leak the window until the event stream is
// completely closed.
let interactive = open(window, "DOMContentLoaded", { capture: true });
let complete = open(window, "load", { capture: true });
let states = merge([interactive, complete]);
let changes = filter(states, ({target}) => target === window.document);
return map(changes, function({type, target}) {
return { type: type, target: target.defaultView }
});
let changes = filter(states, makeStrictDocumentFilter(window));
return map(changes, toEventWithDefaultViewTarget);
}
// In addition to observing windows that are open we also observe windows

Просмотреть файл

@ -0,0 +1,6 @@
[DEFAULT]
support-files =
leak-utils.js
[test-leak-window-events.js]
[test-leak-event-dom-closed-window.js]

Просмотреть файл

@ -0,0 +1,80 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu, Ci } = require("chrome");
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { SelfSupportBackend } = Cu.import("resource:///modules/SelfSupportBackend.jsm", {});
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
// Adapted from the SpecialPowers.exactGC() code. We don't have a
// window to operate on so we cannot use the exact same logic. We
// use 6 GC iterations here as that is what is needed to clean up
// the windows we have tested with.
function gc() {
return new Promise(resolve => {
Cu.forceGC();
Cu.forceCC();
let count = 0;
function genGCCallback() {
Cu.forceCC();
return function() {
if (++count < 5) {
Cu.schedulePreciseGC(genGCCallback());
} else {
resolve();
}
}
}
Cu.schedulePreciseGC(genGCCallback());
});
}
// Execute the given test function and verify that we did not leak windows
// in the process. The test function must return a promise or be a generator.
// If the promise is resolved, or generator completes, with an sdk loader
// object then it will be unloaded after the memory measurements.
exports.asyncWindowLeakTest = function*(assert, asyncTestFunc) {
// SelfSupportBackend periodically tries to open windows. This can
// mess up our window leak detection below, so turn it off.
SelfSupportBackend.uninit();
// Wait for the browser to finish loading.
yield Startup.onceInitialized;
// Track windows that are opened in an array of weak references.
let weakWindows = [];
function windowObserver(subject, topic) {
let supportsWeak = subject.QueryInterface(Ci.nsISupportsWeakReference);
if (supportsWeak) {
weakWindows.push(Cu.getWeakReference(supportsWeak));
}
}
Services.obs.addObserver(windowObserver, "domwindowopened", false);
// Execute the body of the test.
let testLoader = yield asyncTestFunc(assert);
// Stop tracking new windows and attempt to GC any resources allocated
// by the test body.
Services.obs.removeObserver(windowObserver, "domwindowopened", false);
yield gc();
// Check to see if any of the windows we saw survived the GC. We consider
// these leaks.
assert.ok(weakWindows.length > 0, "should see at least one new window");
for (let i = 0; i < weakWindows.length; ++i) {
assert.equal(weakWindows[i].get(), null, "window " + i + " should be GC'd");
}
// Finally, unload the test body's loader if it provided one. We do this
// after our leak detection to avoid free'ing things on unload. Users
// don't tend to unload their addons very often, so we want to find leaks
// that happen while addons are in use.
if (testLoader) {
testLoader.unload();
}
}

Просмотреть файл

@ -0,0 +1,29 @@
/* 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 { asyncWindowLeakTest } = require("./leak-utils");
const { Loader } = require('sdk/test/loader');
const openWindow = require("sdk/window/utils").open;
exports["test sdk/event/dom does not leak when attached to closed window"] = function*(assert) {
yield asyncWindowLeakTest(assert, _ => {
return new Promise(resolve => {
let loader = Loader(module);
let { open } = loader.require('sdk/event/dom');
let w = openWindow();
w.addEventListener("DOMWindowClose", function windowClosed(evt) {
w.removeEventListener("DOMWindowClose", windowClosed);
// The sdk/event/dom module tries to clean itself up when DOMWindowClose
// is fired. Verify that it doesn't leak if its attached to an
// already closed window either. (See bug 1268898.)
open(w.document, "TestEvent1");
resolve(loader);
});
w.close();
});
});
}
require("sdk/test").run(exports);

Просмотреть файл

@ -0,0 +1,47 @@
/* 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";
// Opening new windows in Fennec causes issues
module.metadata = {
engines: {
'Firefox': '*'
}
};
const { asyncWindowLeakTest } = require("./leak-utils.js");
const { Loader } = require("sdk/test/loader");
const { open } = require("sdk/window/utils");
exports["test window/events for leaks"] = function*(assert) {
yield asyncWindowLeakTest(assert, _ => {
return new Promise((resolve, reject) => {
let loader = Loader(module);
let { events } = loader.require("sdk/window/events");
let { on, off } = loader.require("sdk/event/core");
on(events, "data", function handler(e) {
try {
if (e.type === "load") {
e.target.close();
}
else if (e.type === "close") {
off(events, "data", handler);
// Let asyncWindowLeakTest call loader.unload() after the
// leak check.
resolve(loader);
}
} catch (e) {
reject(e);
}
});
// Open a window. This will trigger our data events.
open();
});
});
};
require("sdk/test").run(exports);

Просмотреть файл

@ -1896,8 +1896,10 @@ BrowserGlue.prototype = {
_showSyncStartedDoorhanger: function () {
let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
let productName = gBrandBundle.GetStringFromName("brandShortName");
let title = bundle.GetStringFromName("syncStartNotification.title");
let body = bundle.GetStringFromName("syncStartNotification.body");
let body = bundle.formatStringFromName("syncStartNotification.body2",
[productName], 1);
let clickCallback = (subject, topic, data) => {
if (topic != "alertclickcallback")

Просмотреть файл

@ -6,9 +6,9 @@
"filename": "mozmake.exe"
},
{
"version": "rustc 1.9.0 (e4e8b6668 2016-05-18)",
"size": 82463178,
"digest": "a3c54c6792e75d53ec79caf958db25b651fcf968a37b00949fb327c54a54cad6305a4af302f267082d86d70fcf837ed0f273f85b97706c20b957ff3690889b40",
"version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 88820579,
"digest": "3bc772d951bf90b01cdba9dcd0e1d131a98519dff0710bb219784ea43d4d001dbce191071a4b3824933386bb9613f173760c438939eb396b0e0dfdad9a42e4f0",
"algorithm": "sha512",
"filename": "rustc.tar.bz2",
"unpack": true

Просмотреть файл

@ -6,9 +6,9 @@
"filename": "mozmake.exe"
},
{
"version": "rustc 1.9.0 (e4e8b6668 2016-05-18)",
"size": 88486080,
"digest": "a4fb99cd637b236a9c30e111757ca560bc8df1b143324c1d9ab58c32470b9b9a0598e3e0d220278ee157959dcd88421496388e2ed856e6261d9c81f18e6310e9",
"version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 94067220,
"digest": "05cabda2a28ce6674f062aab589b4b3758e0cd4a4af364bb9a2e736254baa10d668936b2b7ed0df530c7f5ba8ea1e7f51ff3affc84a6551c46188b2f67f10e05",
"algorithm": "sha512",
"visibility": "public",
"filename": "rustc.tar.bz2",

Просмотреть файл

@ -27,7 +27,8 @@ verificationNotSentBody = We are unable to send a verification mail at this time
# LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
# These strings are used in a notification shown after Sync is connected.
syncStartNotification.title = Sync enabled
syncStartNotification.body = Firefox will begin syncing momentarily.
# %S is brandShortName
syncStartNotification.body2 = %S will begin syncing momentarily.
# LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
# These strings are used in a notification shown after Sync was disconnected remotely.

Просмотреть файл

@ -137,14 +137,21 @@ def namespace(**kwargs):
# return namespace(foo=value)
# set_config('FOO', delayed_getattr(option, 'foo')
@template
@imports('__sandbox__')
def delayed_getattr(func, key):
@depends(func)
def result(value):
_, deps = __sandbox__._depends.get(func, (None, ()))
def result(value, _=None):
# The @depends function we're being passed may have returned
# None, or an object that simply doesn't have the wanted key.
# In that case, just return None.
return getattr(value, key, None)
return result
# Automatically add a dependency on --help when the given @depends
# function itself depends on --help.
if __sandbox__._help_option in deps:
return depends(func, '--help')(result)
return depends(func)(result)
# Like @depends, but the decorated function is only called if one of the

Просмотреть файл

@ -71,6 +71,8 @@ included_inclnames_to_ignore = set([
'prcvar.h', # NSPR
'prerror.h', # NSPR
'prinit.h', # NSPR
'prio.h', # NSPR
'private/pprio.h', # NSPR
'prlink.h', # NSPR
'prlock.h', # NSPR
'prprf.h', # NSPR

Просмотреть файл

@ -18,3 +18,4 @@ skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s
[browser_responsive_devicewidth.js]
[browser_responsiveui_customuseragent.js]
[browser_responsiveui_window_close.js]
skip-if = (os == 'linux') && e10s && debug # Bug 1277274

Просмотреть файл

@ -19,7 +19,6 @@ const { ActorClassWithSpec } = require("devtools/shared/protocol");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { assert, dumpn, update, fetch } = DevToolsUtils;
const promise = require("promise");
const PromiseDebugging = require("PromiseDebugging");
const xpcInspector = require("xpcInspector");
const ScriptStore = require("./utils/ScriptStore");
const { DevToolsWorker } = require("devtools/shared/worker/worker");

Просмотреть файл

@ -317,12 +317,6 @@ this.WorkerDebuggerLoader = WorkerDebuggerLoader;
// does not provide alternative definitions for them. Consequently, they are
// stubbed out both on the main thread and worker threads.
var PromiseDebugging = {
getState: function () {
throw new Error("PromiseDebugging is not available in workers!");
}
};
var chrome = {
CC: undefined,
Cc: undefined,
@ -496,7 +490,6 @@ this.worker = new WorkerDebuggerLoader({
loadSubScript: loadSubScript,
modules: {
"Debugger": Debugger,
"PromiseDebugging": PromiseDebugging,
"Services": Object.create(null),
"chrome": chrome,
"xpcInspector": xpcInspector

Просмотреть файл

@ -50,7 +50,11 @@ if (Services.prefs.getBoolPref("javascript.options.asyncstack")) {
ok(frame.asyncParent !== null, "Parent frame has async parent");
is(frame.asyncParent.asyncCause, "promise callback",
"Async parent has correct cause");
is(frame.asyncParent.functionDisplayName, "do_promise",
let asyncFrame = frame.asyncParent;
// Skip over self-hosted parts of our Promise implementation.
while (asyncFrame.source === 'self-hosted')
asyncFrame = asyncFrame.parent;
is(asyncFrame.functionDisplayName, "do_promise",
"Async parent has correct function name");
}
}, {
@ -71,7 +75,11 @@ if (Services.prefs.getBoolPref("javascript.options.asyncstack")) {
ok(frame.asyncParent !== null, "Parent frame has async parent");
is(frame.asyncParent.asyncCause, "promise callback",
"Async parent has correct cause");
is(frame.asyncParent.functionDisplayName, "do_promise_script",
let asyncFrame = frame.asyncParent;
// Skip over self-hosted parts of our Promise implementation.
while (asyncFrame.source === 'self-hosted')
asyncFrame = asyncFrame.parent;
is(asyncFrame.functionDisplayName, "do_promise_script",
"Async parent has correct function name");
}
});

Просмотреть файл

@ -91,12 +91,24 @@ if (Services.prefs.getBoolPref("javascript.options.asyncstack")) {
check: function(markers) {
markers = markers.filter(m => m.name == "ConsoleTime");
ok(markers.length > 0, "Promise marker includes stack");
ok(markers[0].stack.functionDisplayName == "testConsoleTime",
"testConsoleTime is on the stack");
let frame = markers[0].endStack;
ok(frame.parent.asyncParent !== null, "Parent frame has async parent");
is(frame.parent.asyncParent.asyncCause, "promise callback",
ok(frame.functionDisplayName == "testConsoleTimeEnd",
"testConsoleTimeEnd is on the stack");
frame = frame.parent;
ok(frame.functionDisplayName == "makePromise/<",
"makePromise/< is on the stack");
let asyncFrame = frame.asyncParent;
ok(asyncFrame !== null, "Frame has async parent");
is(asyncFrame.asyncCause, "promise callback",
"Async parent has correct cause");
is(frame.parent.asyncParent.functionDisplayName, "makePromise",
// Skip over self-hosted parts of our Promise implementation.
while (asyncFrame.source === 'self-hosted') {
asyncFrame = asyncFrame.parent;
}
is(asyncFrame.functionDisplayName, "makePromise",
"Async parent has correct function name");
}
});

Просмотреть файл

@ -1459,9 +1459,6 @@ nsIDocument::nsIDocument()
mUserHasInteracted(false)
{
SetIsDocument();
if (IsStyledByServo()) {
SetFlags(NODE_IS_DIRTY_FOR_SERVO | NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
}
PR_INIT_CLIST(&mDOMMediaQueryLists);
}

Просмотреть файл

@ -143,9 +143,12 @@ ThrowNoSetterArg(JSContext* aCx, prototypes::ID aProtoId)
} // namespace dom
struct ErrorResult::Message {
Message() { MOZ_COUNT_CTOR(ErrorResult::Message); }
~Message() { MOZ_COUNT_DTOR(ErrorResult::Message); }
namespace binding_danger {
template<typename CleanupPolicy>
struct TErrorResult<CleanupPolicy>::Message {
Message() { MOZ_COUNT_CTOR(TErrorResult::Message); }
~Message() { MOZ_COUNT_DTOR(TErrorResult::Message); }
nsTArray<nsString> mArgs;
dom::ErrNum mErrorNumber;
@ -156,9 +159,12 @@ struct ErrorResult::Message {
}
};
template<typename CleanupPolicy>
nsTArray<nsString>&
ErrorResult::CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType)
TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(const dom::ErrNum errorNumber,
nsresult errorType)
{
AssertInOwningThread();
mResult = errorType;
mMessage = new Message();
@ -166,20 +172,25 @@ ErrorResult::CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult er
return mMessage->mArgs;
}
template<typename CleanupPolicy>
void
ErrorResult::SerializeMessage(IPC::Message* aMsg) const
TErrorResult<CleanupPolicy>::SerializeMessage(IPC::Message* aMsg) const
{
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mMessage);
WriteParam(aMsg, mMessage->mArgs);
WriteParam(aMsg, mMessage->mErrorNumber);
}
template<typename CleanupPolicy>
bool
ErrorResult::DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter)
TErrorResult<CleanupPolicy>::DeserializeMessage(const IPC::Message* aMsg,
PickleIterator* aIter)
{
using namespace IPC;
AssertInOwningThread();
nsAutoPtr<Message> readMessage(new Message());
if (!ReadParam(aMsg, aIter, &readMessage->mArgs) ||
!ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) {
@ -197,9 +208,11 @@ ErrorResult::DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter)
return true;
}
template<typename CleanupPolicy>
void
ErrorResult::SetPendingExceptionWithMessage(JSContext* aCx)
TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(JSContext* aCx)
{
AssertInOwningThread();
MOZ_ASSERT(mMessage, "SetPendingExceptionWithMessage() can be called only once");
MOZ_ASSERT(mUnionState == HasMessage);
@ -220,9 +233,11 @@ ErrorResult::SetPendingExceptionWithMessage(JSContext* aCx)
mResult = NS_OK;
}
template<typename CleanupPolicy>
void
ErrorResult::ClearMessage()
TErrorResult<CleanupPolicy>::ClearMessage()
{
AssertInOwningThread();
MOZ_ASSERT(IsErrorWithMessage());
delete mMessage;
mMessage = nullptr;
@ -231,9 +246,11 @@ ErrorResult::ClearMessage()
#endif // DEBUG
}
template<typename CleanupPolicy>
void
ErrorResult::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
TErrorResult<CleanupPolicy>::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
{
AssertInOwningThread();
MOZ_ASSERT(mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to throw a JS exception?");
@ -243,7 +260,7 @@ ErrorResult::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
// don't set it to exn yet, because we don't want to do that until after we
// root.
mJSException.setUndefined();
if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) {
// Don't use NS_ERROR_DOM_JS_EXCEPTION, because that indicates we have
// in fact rooted mJSException.
mResult = NS_ERROR_OUT_OF_MEMORY;
@ -256,9 +273,11 @@ ErrorResult::ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn)
}
}
template<typename CleanupPolicy>
void
ErrorResult::SetPendingJSException(JSContext* cx)
TErrorResult<CleanupPolicy>::SetPendingJSException(JSContext* cx)
{
AssertInOwningThread();
MOZ_ASSERT(!mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to handle JS exceptions?");
MOZ_ASSERT(mUnionState == HasJSException);
@ -278,7 +297,8 @@ ErrorResult::SetPendingJSException(JSContext* cx)
#endif // DEBUG
}
struct ErrorResult::DOMExceptionInfo {
template<typename CleanupPolicy>
struct TErrorResult<CleanupPolicy>::DOMExceptionInfo {
DOMExceptionInfo(nsresult rv, const nsACString& message)
: mMessage(message)
, mRv(rv)
@ -288,20 +308,25 @@ struct ErrorResult::DOMExceptionInfo {
nsresult mRv;
};
template<typename CleanupPolicy>
void
ErrorResult::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(IPC::Message* aMsg) const
{
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mDOMExceptionInfo);
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
WriteParam(aMsg, mDOMExceptionInfo->mMessage);
WriteParam(aMsg, mDOMExceptionInfo->mRv);
}
template<typename CleanupPolicy>
bool
ErrorResult::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter)
TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(const IPC::Message* aMsg,
PickleIterator* aIter)
{
using namespace IPC;
AssertInOwningThread();
nsCString message;
nsresult rv;
if (!ReadParam(aMsg, aIter, &message) ||
@ -318,9 +343,12 @@ ErrorResult::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterato
return true;
}
template<typename CleanupPolicy>
void
ErrorResult::ThrowDOMException(nsresult rv, const nsACString& message)
TErrorResult<CleanupPolicy>::ThrowDOMException(nsresult rv,
const nsACString& message)
{
AssertInOwningThread();
ClearUnionData();
mResult = NS_ERROR_DOM_DOMEXCEPTION;
@ -330,9 +358,11 @@ ErrorResult::ThrowDOMException(nsresult rv, const nsACString& message)
#endif
}
template<typename CleanupPolicy>
void
ErrorResult::SetPendingDOMException(JSContext* cx)
TErrorResult<CleanupPolicy>::SetPendingDOMException(JSContext* cx)
{
AssertInOwningThread();
MOZ_ASSERT(mDOMExceptionInfo,
"SetPendingDOMException() can be called only once");
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
@ -343,9 +373,11 @@ ErrorResult::SetPendingDOMException(JSContext* cx)
mResult = NS_OK;
}
template<typename CleanupPolicy>
void
ErrorResult::ClearDOMExceptionInfo()
TErrorResult<CleanupPolicy>::ClearDOMExceptionInfo()
{
AssertInOwningThread();
MOZ_ASSERT(IsDOMException());
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo || !mDOMExceptionInfo);
delete mDOMExceptionInfo;
@ -355,9 +387,11 @@ ErrorResult::ClearDOMExceptionInfo()
#endif // DEBUG
}
template<typename CleanupPolicy>
void
ErrorResult::ClearUnionData()
TErrorResult<CleanupPolicy>::ClearUnionData()
{
AssertInOwningThread();
if (IsJSException()) {
JSContext* cx = nsContentUtils::RootingCx();
MOZ_ASSERT(cx);
@ -373,9 +407,11 @@ ErrorResult::ClearUnionData()
}
}
template<typename CleanupPolicy>
void
ErrorResult::SetPendingGenericErrorException(JSContext* cx)
TErrorResult<CleanupPolicy>::SetPendingGenericErrorException(JSContext* cx)
{
AssertInOwningThread();
MOZ_ASSERT(!IsErrorWithMessage());
MOZ_ASSERT(!IsJSException());
MOZ_ASSERT(!IsDOMException());
@ -383,9 +419,12 @@ ErrorResult::SetPendingGenericErrorException(JSContext* cx)
mResult = NS_OK;
}
ErrorResult&
ErrorResult::operator=(ErrorResult&& aRHS)
template<typename CleanupPolicy>
TErrorResult<CleanupPolicy>&
TErrorResult<CleanupPolicy>::operator=(TErrorResult<CleanupPolicy>&& aRHS)
{
AssertInOwningThread();
aRHS.AssertInOwningThread();
// Clear out any union members we may have right now, before we
// start writing to it.
ClearUnionData();
@ -401,7 +440,7 @@ ErrorResult::operator=(ErrorResult&& aRHS)
JSContext* cx = nsContentUtils::RootingCx();
MOZ_ASSERT(cx);
mJSException.setUndefined();
if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) {
if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) {
MOZ_CRASH("Could not root mJSException, we're about to OOM");
}
mJSException = aRHS.mJSException;
@ -427,9 +466,13 @@ ErrorResult::operator=(ErrorResult&& aRHS)
return *this;
}
template<typename CleanupPolicy>
void
ErrorResult::CloneTo(ErrorResult& aRv) const
TErrorResult<CleanupPolicy>::CloneTo(TErrorResult& aRv) const
{
AssertInOwningThread();
aRv.AssertInOwningThread();
aRv.ClearUnionData();
aRv.mResult = mResult;
#ifdef DEBUG
@ -459,9 +502,11 @@ ErrorResult::CloneTo(ErrorResult& aRv) const
}
}
template<typename CleanupPolicy>
void
ErrorResult::SuppressException()
TErrorResult<CleanupPolicy>::SuppressException()
{
AssertInOwningThread();
WouldReportJSException();
ClearUnionData();
// We don't use AssignErrorCode, because we want to override existing error
@ -469,9 +514,11 @@ ErrorResult::SuppressException()
mResult = NS_OK;
}
template<typename CleanupPolicy>
void
ErrorResult::SetPendingException(JSContext* cx)
TErrorResult<CleanupPolicy>::SetPendingException(JSContext* cx)
{
AssertInOwningThread();
if (IsUncatchableException()) {
// Nuke any existing exception on cx, to make sure we're uncatchable.
JS_ClearPendingException(cx);
@ -501,9 +548,11 @@ ErrorResult::SetPendingException(JSContext* cx)
SetPendingGenericErrorException(cx);
}
template<typename CleanupPolicy>
void
ErrorResult::StealExceptionFromJSContext(JSContext* cx)
TErrorResult<CleanupPolicy>::StealExceptionFromJSContext(JSContext* cx)
{
AssertInOwningThread();
MOZ_ASSERT(mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to throw a JS exception?");
@ -517,9 +566,11 @@ ErrorResult::StealExceptionFromJSContext(JSContext* cx)
JS_ClearPendingException(cx);
}
template<typename CleanupPolicy>
void
ErrorResult::NoteJSContextException(JSContext* aCx)
TErrorResult<CleanupPolicy>::NoteJSContextException(JSContext* aCx)
{
AssertInOwningThread();
if (JS_IsExceptionPending(aCx)) {
mResult = NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
} else {
@ -527,6 +578,12 @@ ErrorResult::NoteJSContextException(JSContext* aCx)
}
}
template class TErrorResult<JustAssertCleanupPolicy>;
template class TErrorResult<AssertAndSuppressCleanupPolicy>;
template class TErrorResult<JustSuppressCleanupPolicy>;
} // namespace binding_danger
namespace dom {
bool

Просмотреть файл

@ -106,7 +106,7 @@ IsNonProxyDOMClass(const JSClass* clasp)
return IsNonProxyDOMClass(js::Valueify(clasp));
}
// Returns true if the JSClass is used for DOM interface and interface
// Returns true if the JSClass is used for DOM interface and interface
// prototype objects.
inline bool
IsDOMIfaceAndProtoClass(const JSClass* clasp)
@ -2011,6 +2011,12 @@ private:
};
};
class FastErrorResult :
public mozilla::binding_danger::TErrorResult<
mozilla::binding_danger::JustAssertCleanupPolicy>
{
};
} // namespace binding_detail
enum StringificationBehavior {

Просмотреть файл

@ -5212,7 +5212,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
templateBody = fill(
"""
{ // Scope for our GlobalObject, ErrorResult, JSAutoCompartment,
{ // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment,
// etc.
JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx));
@ -5227,7 +5227,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if (!JS_WrapValue(cx, &valueToResolve)) {
$*{exceptionCode}
}
ErrorResult promiseRv;
binding_detail::FastErrorResult promiseRv;
#ifdef SPIDERMONKEY_PROMISE
nsCOMPtr<nsIGlobalObject> global =
do_QueryInterface(promiseGlobal.GetAsSupports());
@ -6922,7 +6922,7 @@ class CGCallGenerator(CGThing):
self.cgRoot.append(call)
if isFallible:
self.cgRoot.prepend(CGGeneric("ErrorResult rv;\n"))
self.cgRoot.prepend(CGGeneric("binding_detail::FastErrorResult rv;\n"))
self.cgRoot.append(CGGeneric(dedent(
"""
if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx))) {
@ -8437,7 +8437,7 @@ class CGEnumerateHook(CGAbstractBindingMethod):
def generate_code(self):
return CGGeneric(dedent("""
AutoTArray<nsString, 8> names;
ErrorResult rv;
binding_detail::FastErrorResult rv;
self->GetOwnPropertyNames(cx, names, rv);
if (rv.MaybeSetPendingException(cx)) {
return false;
@ -10530,7 +10530,7 @@ class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod):
def generate_code(self):
return CGGeneric(dedent("""
AutoTArray<nsString, 8> names;
ErrorResult rv;
binding_detail::FastErrorResult rv;
self->GetOwnPropertyNames(cx, names, rv);
if (rv.MaybeSetPendingException(cx)) {
return false;

Просмотреть файл

@ -5,9 +5,10 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* A struct for tracking exceptions that need to be thrown to JS.
* A set of structs for tracking exceptions that need to be thrown to JS:
* ErrorResult and IgnoredErrorResult.
*
* Conceptually, an ErrorResult represents either success or an exception in the
* Conceptually, these structs represent either success or an exception in the
* process of being thrown. This means that a failing ErrorResult _must_ be
* handled in one of the following ways before coming off the stack:
*
@ -17,6 +18,8 @@
* MaybeSetPendingException.
* 4) Converted to an exception JS::Value (probably to then reject a Promise
* with) via dom::ToJSValue.
*
* An IgnoredErrorResult will automatically do the first of those four things.
*/
#ifndef mozilla_ErrorResult_h
@ -31,6 +34,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Move.h"
#include "nsTArray.h"
#include "nsISupportsImpl.h"
namespace IPC {
class Message;
@ -87,9 +91,25 @@ struct StringArrayAppender
} // namespace dom
class ErrorResult {
class ErrorResult;
namespace binding_danger {
/**
* Templated implementation class for various ErrorResult-like things. The
* instantiations differ only in terms of their cleanup policies (used in the
* destructor), which they can specify via the template argument. Note that
* this means it's safe to reinterpret_cast between the instantiations unless
* you plan to invoke the destructor through such a cast pointer.
*
* A cleanup policy consists of two booleans: whether to assert that we've been
* reported or suppressed, and whether to then go ahead and suppress the
* exception.
*/
template<typename CleanupPolicy>
class TErrorResult {
public:
ErrorResult()
TErrorResult()
: mResult(NS_OK)
#ifdef DEBUG
, mMightHaveUnreportedJSException(false)
@ -98,49 +118,58 @@ public:
{
}
#ifdef DEBUG
~ErrorResult() {
// Consumers should have called one of MaybeSetPendingException
// (possibly via ToJSValue), StealNSResult, and SuppressException
MOZ_ASSERT(!Failed());
MOZ_ASSERT(!mMightHaveUnreportedJSException);
MOZ_ASSERT(mUnionState == HasNothing);
}
#endif // DEBUG
~TErrorResult() {
AssertInOwningThread();
ErrorResult(ErrorResult&& aRHS)
if (CleanupPolicy::assertHandled) {
// Consumers should have called one of MaybeSetPendingException
// (possibly via ToJSValue), StealNSResult, and SuppressException
AssertReportedOrSuppressed();
}
if (CleanupPolicy::suppress) {
SuppressException();
}
// And now assert that we're in a good final state.
AssertReportedOrSuppressed();
}
TErrorResult(TErrorResult&& aRHS)
// Initialize mResult and whatever else we need to default-initialize, so
// the ClearUnionData call in our operator= will do the right thing
// (nothing).
: ErrorResult()
: TErrorResult()
{
*this = Move(aRHS);
}
ErrorResult& operator=(ErrorResult&& aRHS);
TErrorResult& operator=(TErrorResult&& aRHS);
explicit ErrorResult(nsresult aRv)
: ErrorResult()
explicit TErrorResult(nsresult aRv)
: TErrorResult()
{
AssignErrorCode(aRv);
}
operator ErrorResult&();
void Throw(nsresult rv) {
MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
AssignErrorCode(rv);
}
// Duplicate our current state on the given ErrorResult object. Any existing
// errors or messages on the target will be suppressed before cloning. Our
// own error state remains unchanged.
void CloneTo(ErrorResult& aRv) const;
// Duplicate our current state on the given TErrorResult object. Any
// existing errors or messages on the target will be suppressed before
// cloning. Our own error state remains unchanged.
void CloneTo(TErrorResult& aRv) const;
// Use SuppressException when you want to suppress any exception that might be
// on the ErrorResult. After this call, the ErrorResult will be back a "no
// on the TErrorResult. After this call, the TErrorResult will be back a "no
// exception thrown" state.
void SuppressException();
// Use StealNSResult() when you want to safely convert the ErrorResult to an
// nsresult that you will then return to a caller. This will
// Use StealNSResult() when you want to safely convert the TErrorResult to
// an nsresult that you will then return to a caller. This will
// SuppressException(), since there will no longer be a way to report it.
nsresult StealNSResult() {
nsresult rv = ErrorCode();
@ -148,11 +177,11 @@ public:
return rv;
}
// Use MaybeSetPendingException to convert an ErrorResult to a pending
// Use MaybeSetPendingException to convert a TErrorResult to a pending
// exception on the given JSContext. This is the normal "throw an exception"
// codepath.
//
// The return value is false if the ErrorResult represents success, true
// The return value is false if the TErrorResult represents success, true
// otherwise. This does mean that in JSAPI method implementations you can't
// just use this as |return rv.MaybeSetPendingException(cx)| (though you could
// |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any
@ -173,7 +202,7 @@ public:
// considered equivalent to a JSAPI failure in terms of what callers should do
// after true is returned.
//
// After this call, the ErrorResult will no longer return true from Failed(),
// After this call, the TErrorResult will no longer return true from Failed(),
// since the exception will have moved to the JSContext.
bool MaybeSetPendingException(JSContext* cx)
{
@ -187,7 +216,7 @@ public:
}
// Use StealExceptionFromJSContext to convert a pending exception on a
// JSContext to an ErrorResult. This function must be called only when a
// JSContext to a TErrorResult. This function must be called only when a
// JSAPI operation failed. It assumes that lack of pending exception on the
// JSContext means an uncatchable exception was thrown.
//
@ -215,11 +244,11 @@ public:
bool IsErrorWithMessage() const { return ErrorCode() == NS_ERROR_TYPE_ERR || ErrorCode() == NS_ERROR_RANGE_ERR; }
// Facilities for throwing a preexisting JS exception value via this
// ErrorResult. The contract is that any code which might end up calling
// TErrorResult. The contract is that any code which might end up calling
// ThrowJSException() or StealExceptionFromJSContext() must call
// MightThrowJSException() even if no exception is being thrown. Code that
// conditionally calls ToJSValue on this ErrorResult only if Failed() must
// first call WouldReportJSException even if this ErrorResult has not failed.
// conditionally calls ToJSValue on this TErrorResult only if Failed() must
// first call WouldReportJSException even if this TErrorResult has not failed.
//
// The exn argument to ThrowJSException can be in any compartment. It does
// not have to be in the compartment of cx. If someone later uses it, they
@ -235,12 +264,12 @@ public:
void ThrowDOMException(nsresult rv, const nsACString& message = EmptyCString());
bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_DOMEXCEPTION; }
// Flag on the ErrorResult that whatever needs throwing has been
// Flag on the TErrorResult that whatever needs throwing has been
// thrown on the JSContext already and we should not mess with it.
// If nothing was thrown, this becomes an uncatchable exception.
void NoteJSContextException(JSContext* aCx);
// Check whether the ErrorResult says to just throw whatever is on
// Check whether the TErrorResult says to just throw whatever is on
// the JSContext already.
bool IsJSContextException() {
return ErrorCode() == NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
@ -306,6 +335,7 @@ private:
};
#endif // DEBUG
friend struct IPC::ParamTraits<TErrorResult>;
friend struct IPC::ParamTraits<ErrorResult>;
void SerializeMessage(IPC::Message* aMsg) const;
bool DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter);
@ -313,7 +343,7 @@ private:
void SerializeDOMExceptionInfo(IPC::Message* aMsg) const;
bool DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter);
// Helper method that creates a new Message for this ErrorResult,
// Helper method that creates a new Message for this TErrorResult,
// and returns the arguments array from that Message.
nsTArray<nsString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType);
@ -336,6 +366,12 @@ private:
#endif // DEBUG
}
MOZ_ALWAYS_INLINE void AssertInOwningThread() const {
#ifdef DEBUG
NS_ASSERT_OWNINGTHREAD(TErrorResult);
#endif
}
void AssignErrorCode(nsresult aRv) {
MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()");
@ -371,6 +407,12 @@ private:
void SetPendingDOMException(JSContext* cx);
void SetPendingGenericErrorException(JSContext* cx);
MOZ_ALWAYS_INLINE void AssertReportedOrSuppressed()
{
MOZ_ASSERT(!Failed());
MOZ_ASSERT(!mMightHaveUnreportedJSException);
MOZ_ASSERT(mUnionState == HasNothing);
}
// Special values of mResult:
// NS_ERROR_TYPE_ERR -- ThrowTypeError() called on us.
@ -404,22 +446,85 @@ private:
// we should have something, if we have already cleaned up the
// something.
UnionState mUnionState;
// The thread that created this TErrorResult
NS_DECL_OWNINGTHREAD;
#endif
// Not to be implemented, to make sure people always pass this by
// reference, not by value.
TErrorResult(const TErrorResult&) = delete;
void operator=(const TErrorResult&) = delete;
};
struct JustAssertCleanupPolicy {
static const bool assertHandled = true;
static const bool suppress = false;
};
struct AssertAndSuppressCleanupPolicy {
static const bool assertHandled = true;
static const bool suppress = true;
};
struct JustSuppressCleanupPolicy {
static const bool assertHandled = false;
static const bool suppress = true;
};
} // namespace binding_danger
// A class people should normally use on the stack when they plan to actually
// do something with the exception.
class ErrorResult :
public binding_danger::TErrorResult<binding_danger::AssertAndSuppressCleanupPolicy>
{
typedef binding_danger::TErrorResult<binding_danger::AssertAndSuppressCleanupPolicy> BaseErrorResult;
public:
ErrorResult()
: BaseErrorResult()
{}
ErrorResult(ErrorResult&& aRHS)
: BaseErrorResult(Move(aRHS))
{}
explicit ErrorResult(nsresult aRv)
: BaseErrorResult(aRv)
{}
void operator=(nsresult rv)
{
BaseErrorResult::operator=(rv);
}
ErrorResult& operator=(ErrorResult&& aRHS)
{
BaseErrorResult::operator=(Move(aRHS));
return *this;
}
private:
// Not to be implemented, to make sure people always pass this by
// reference, not by value.
ErrorResult(const ErrorResult&) = delete;
void operator=(const ErrorResult&) = delete;
};
// A class for use when an ErrorResult should just automatically be ignored.
class IgnoredErrorResult : public ErrorResult
template<typename CleanupPolicy>
binding_danger::TErrorResult<CleanupPolicy>::operator ErrorResult&()
{
return *static_cast<ErrorResult*>(
reinterpret_cast<TErrorResult<AssertAndSuppressCleanupPolicy>*>(this));
}
// A class for use when an ErrorResult should just automatically be ignored.
// This doesn't inherit from ErrorResult so we don't make two separate calls to
// SuppressException.
class IgnoredErrorResult :
public binding_danger::TErrorResult<binding_danger::JustSuppressCleanupPolicy>
{
public:
~IgnoredErrorResult()
{
SuppressException();
}
};
/******************************************************************************

Просмотреть файл

@ -25,7 +25,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
"Should have the right message in test " + testNumber);
is(exn.code, code, "Should have the right .code in test " + testNumber);
if (message === "") {
is(exn.name, "NS_ERROR_UNEXPECTED",
is(exn.name, "InternalError",
"Should have one of our synthetic exceptions in test " + testNumber);
}
is(exn.stack, stack, "Should have the right stack in test " + testNumber);
@ -41,79 +41,88 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
var isE10S = !SpecialPowers.isMainProcess();
var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
var ourFile = location.href;
var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:121:3
var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper.";
var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:130:3
` : "";
Promise.all([
t.testPromiseWithThrowingChromePromiseInit().then(
ensurePromiseFail.bind(null, 1),
checkExn.bind(null, 48, "NS_ERROR_UNEXPECTED", "", undefined,
ourFile, 1,
`doTest@${ourFile}:48:7
checkExn.bind(null, 49, "InternalError", unwrapError,
undefined, ourFile, 1,
`doTest@${ourFile}:49:7
` +
parentFrame)),
t.testPromiseWithThrowingContentPromiseInit(function() {
thereIsNoSuchContentFunction1();
}).then(
ensurePromiseFail.bind(null, 2),
checkExn.bind(null, 56, "ReferenceError",
checkExn.bind(null, 57, "ReferenceError",
"thereIsNoSuchContentFunction1 is not defined",
undefined, ourFile, 2,
`doTest/<@${ourFile}:56:11
doTest@${ourFile}:55:7
`doTest/<@${ourFile}:57:11
doTest@${ourFile}:56:7
` +
parentFrame)),
t.testPromiseWithThrowingChromeThenFunction().then(
ensurePromiseFail.bind(null, 3),
checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 3, asyncStack ? (`Async*doTest@${ourFile}:67:7
` +
parentFrame) : "")),
t.testPromiseWithThrowingContentThenFunction(function() {
thereIsNoSuchContentFunction2();
}).then(
ensurePromiseFail.bind(null, 4),
checkExn.bind(null, 70, "ReferenceError",
checkExn.bind(null, 73, "ReferenceError",
"thereIsNoSuchContentFunction2 is not defined",
undefined, ourFile, 4,
`doTest/<@${ourFile}:70:11
`doTest/<@${ourFile}:73:11
` +
(asyncStack ? `Async*doTest@${ourFile}:69:7
(asyncStack ? `Async*doTest@${ourFile}:72:7
` : "") +
parentFrame)),
t.testPromiseWithThrowingChromeThenable().then(
ensurePromiseFail.bind(null, 5),
checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 5, asyncStack ? (`Async*doTest@${ourFile}:84:7
` +
parentFrame) : "")),
t.testPromiseWithThrowingContentThenable({
then: function() { thereIsNoSuchContentFunction3(); }
}).then(
ensurePromiseFail.bind(null, 6),
checkExn.bind(null, 85, "ReferenceError",
checkExn.bind(null, 90, "ReferenceError",
"thereIsNoSuchContentFunction3 is not defined",
undefined, ourFile, 6,
`doTest/<.then@${ourFile}:85:32
`)),
`doTest/<.then@${ourFile}:90:32
` + (asyncStack ? `Async*doTest@${ourFile}:89:7\n` + parentFrame : ""))),
t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
ensurePromiseFail.bind(null, 7),
checkExn.bind(null, 93, "NotFoundError",
checkExn.bind(null, 98, "NotFoundError",
"We are a second DOMException",
DOMException.NOT_FOUND_ERR, ourFile, 7,
`doTest@${ourFile}:93:7
`doTest@${ourFile}:98:7
` +
parentFrame)),
t.testPromiseWithDOMExceptionThrowingThenFunction().then(
ensurePromiseFail.bind(null, 8),
checkExn.bind(null, asyncStack ? 101 : 0, "NetworkError",
checkExn.bind(null, asyncStack ? 106 : 0, "NetworkError",
"We are a third DOMException",
DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
(asyncStack ? `Async*doTest@${ourFile}:101:7
(asyncStack ? `Async*doTest@${ourFile}:106:7
` +
parentFrame : ""))),
t.testPromiseWithDOMExceptionThrowingThenable().then(
ensurePromiseFail.bind(null, 9),
checkExn.bind(null, 0, "TypeMismatchError",
checkExn.bind(null, asyncStack ? 114 : 0, "TypeMismatchError",
"We are a fourth DOMException",
DOMException.TYPE_MISMATCH_ERR, "", 9, "")),
DOMException.TYPE_MISMATCH_ERR,
asyncStack ? ourFile : "", 9,
(asyncStack ? `Async*doTest@${ourFile}:114:7
` +
parentFrame : ""))),
]).then(SimpleTest.finish,
function() {
ok(false, "One of our catch statements totally failed");
function(err) {
ok(false, "One of our catch statements totally failed with err" + err + ', stack: ' + (err ? err.stack : ''));
SimpleTest.finish();
});
}

Просмотреть файл

@ -7,12 +7,12 @@
</head>
<body>
<input id="entries" type="file"></input>
<script type="application/javascript;version=1.7">
var fileEntry;
var directoryEntry;
var script;
var entries;
function setup_tests() {
SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true],
@ -20,12 +20,14 @@ function setup_tests() {
}
function populate_entries() {
entries = document.createElement('input');
entries.setAttribute('type', 'file');
document.body.appendChild(entries);
var url = SimpleTest.getTestFileURL("script_entries.js");
script = SpecialPowers.loadChromeScript(url);
function onOpened(message) {
var entries = document.getElementById('entries');
for (var i = 0 ; i < message.data.length; ++i) {
if (message.data[i] instanceof File) {
SpecialPowers.wrap(entries).mozSetFileArray([message.data[i]]);
@ -39,7 +41,6 @@ function populate_entries() {
}
function test_entries() {
var entries = document.getElementById('entries');
ok("webkitEntries" in entries, "HTMLInputElement.webkitEntries");
is(entries.webkitEntries.length, 0, "HTMLInputElement.webkitEntries.length == 0");
is(entries.files.length, 1, "HTMLInputElement.files is still populated");

Просмотреть файл

@ -563,18 +563,10 @@ nsGeolocationRequest::SetTimeoutTimer()
{
StopTimeoutTimer();
int32_t timeout;
if (mOptions && (timeout = mOptions->mTimeout) != 0) {
if (timeout < 0) {
timeout = 0;
} else if (timeout < 10) {
timeout = 10;
}
if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
mTimeoutTimer->InitWithCallback(holder, timeout, nsITimer::TYPE_ONE_SHOT);
mTimeoutTimer->InitWithCallback(holder, mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
}
}

Просмотреть файл

@ -3412,7 +3412,8 @@ HTMLInputElement::IsNodeApzAwareInternal() const
{
// Tell APZC we may handle mouse wheel event and do preventDefault when input
// type is number.
return (mType == NS_FORM_INPUT_NUMBER) || nsINode::IsNodeApzAwareInternal();
return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
nsINode::IsNodeApzAwareInternal();
}
#endif
@ -4507,17 +4508,30 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
#if defined(XP_WIN) || defined(XP_LINUX)
case eWheel: {
// Handle wheel events as increasing / decreasing the input element's
// value when it's focused and it's type is number.
// value when it's focused and it's type is number or range.
WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
if (!aVisitor.mEvent->DefaultPrevented() &&
aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
wheelEvent->mDeltaY != 0 &&
wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL &&
mType == NS_FORM_INPUT_NUMBER) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && numberControlFrame->IsFocused()) {
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
if (mType == NS_FORM_INPUT_NUMBER) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && numberControlFrame->IsFocused()) {
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
aVisitor.mEvent->PreventDefault();
}
} else if (mType == NS_FORM_INPUT_RANGE &&
nsContentUtils::IsFocusedContent(this) &&
GetMinimum() < GetMaximum()) {
Decimal value = GetValueAsDecimal();
Decimal step = GetStep();
if (step == kStepAny) {
step = GetDefaultStep();
}
MOZ_ASSERT(value.isFinite() && step.isFinite());
SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
value + step : value - step);
aVisitor.mEvent->PreventDefault();
}
}
@ -6014,7 +6028,7 @@ void
HTMLInputElement::UpdateApzAwareFlag()
{
#if defined(XP_WIN) || defined(XP_LINUX)
if (mType == NS_FORM_INPUT_NUMBER) {
if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
SetMayBeApzAware();
}
#endif

Просмотреть файл

@ -620,5 +620,9 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
[test_bug1260664.html]
[test_bug1261673.html]
skip-if = (os != 'win' && os != 'linux')
[test_bug1261674-1.html]
skip-if = (os != 'win' && os != 'linux')
[test_bug1261674-2.html]
skip-if = (os != 'win' && os != 'linux')
[test_bug1260704.html]
[test_allowMedia.html]

Просмотреть файл

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1261674
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1261674</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/paint_listener.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=1261674">Mozilla Bug 1261674</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input id="test_input" type="range" value=5 max=10 min=0>
<script type="text/javascript">
/** Test for Bug 1261674 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
function runTests() {
let input = window.document.getElementById("test_input");
// focus: whether the target input element is focused
// deltaY: deltaY of WheelEvent
// deltaMode: deltaMode of WheelEvent
// valueChanged: expected value changes after input element handled the wheel event
let params = [
{focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: -1},
{focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 1},
{focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: -1},
{focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: 1},
{focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
{focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
{focus: false, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0},
{focus: false, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0}
];
let testIdx = 0;
let result = parseInt(input.value);
function runNext() {
let p = params[testIdx];
(p["focus"]) ? input.focus() : input.blur();
result += parseInt(p["valueChanged"]);
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
ok(input.value == result,
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
(++testIdx >= params.length) ? SimpleTest.finish() : runNext();
});
}
input.addEventListener("input", () => {
ok(input.value == result,
"Test-" + testIdx + " receive input event, expect " + result + " get " + input.value);
}, false);
runNext();
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1261674
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1261674</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/paint_listener.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=1261674">Mozilla Bug 1261674</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input id="test_input" type="range" max=0 min=10>
<script type="text/javascript">
/** Test for Bug 1261674 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
function runTests() {
let input = window.document.getElementById("test_input");
// deltaY: deltaY of WheelEvent
// deltaMode: deltaMode of WheelEvent
let params = [
{deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
{deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
{deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE},
{deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE},
{deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL},
{deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL},
{deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
{deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE}
];
let testIdx = 0;
let result = parseInt(input.value);
function runNext() {
let p = params[testIdx];
(p["focus"]) ? input.focus() : input.blur();
sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
ok(input.value == result,
"Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
testIdx++;
(testIdx >= params.length) ? SimpleTest.finish() : runNext();
});
}
input.addEventListener("input", () => {
ok(false, "Wheel event should be no effect to range input element with max < min");
}, false);
runNext();
}
</script>
</body>
</html>

Просмотреть файл

@ -188,9 +188,8 @@ PresentationConnection::Close(ErrorResult& aRv)
void
PresentationConnection::Terminate(ErrorResult& aRv)
{
// It only works when the state is CONNECTED or CONNECTING.
if (NS_WARN_IF(mState != PresentationConnectionState::Connected &&
mState != PresentationConnectionState::Connecting)) {
// It only works when the state is CONNECTED.
if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
return;
}
@ -282,6 +281,11 @@ PresentationConnection::ProcessStateChanged(nsresult aReason)
return RemoveFromLoadGroup();
}
case PresentationConnectionState::Terminated: {
// Ensure onterminate event is fired.
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent()));
nsCOMPtr<nsIPresentationService> service =
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
if (NS_WARN_IF(!service)) {
@ -293,10 +297,6 @@ PresentationConnection::ProcessStateChanged(nsresult aReason)
return rv;
}
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent()));
return RemoveFromLoadGroup();
}
default:

Просмотреть файл

@ -14,6 +14,7 @@
#include "nsIObserverService.h"
#include "nsXULAppAPI.h"
#include "PresentationSessionRequest.h"
#include "PresentationTerminateRequest.h"
namespace mozilla {
namespace dom {
@ -239,6 +240,28 @@ PresentationDeviceManager::OnSessionRequest(nsIPresentationDevice* aDevice,
return NS_OK;
}
NS_IMETHODIMP
PresentationDeviceManager::OnTerminateRequest(nsIPresentationDevice* aDevice,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
NS_ENSURE_ARG(aDevice);
NS_ENSURE_ARG(aControlChannel);
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
RefPtr<PresentationTerminateRequest> request =
new PresentationTerminateRequest(aDevice, aPresentationId,
aControlChannel, aIsFromReceiver);
obs->NotifyObservers(request,
PRESENTATION_TERMINATE_REQUEST_TOPIC,
nullptr);
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
PresentationDeviceManager::Observe(nsISupports *aSubject,

Просмотреть файл

@ -4,9 +4,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PresentationService.h"
#include "ipc/PresentationIPCService.h"
#include "mozilla/Services.h"
#include "mozIApplication.h"
#include "nsGlobalWindow.h"
#include "nsIAppsService.h"
#include "nsIObserverService.h"
#include "nsIPresentationControlChannel.h"
@ -15,12 +18,12 @@
#include "nsIPresentationListener.h"
#include "nsIPresentationRequestUIGlue.h"
#include "nsIPresentationSessionRequest.h"
#include "nsIPresentationTerminateRequest.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "PresentationLog.h"
#include "PresentationService.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -28,6 +31,31 @@ using namespace mozilla::dom;
namespace mozilla {
namespace dom {
static bool
IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnother) {
if (!aDevice || !aDeviceAnother) {
return false;
}
nsAutoCString deviceId;
aDevice->GetId(deviceId);
nsAutoCString anotherId;
aDeviceAnother->GetId(anotherId);
if (!deviceId.Equals(anotherId)) {
return false;
}
nsAutoCString deviceType;
aDevice->GetType(deviceType);
nsAutoCString anotherType;
aDeviceAnother->GetType(anotherType);
if (!deviceType.Equals(anotherType)) {
return false;
}
return true;
}
/*
* Implementation of PresentationDeviceRequest
*/
@ -191,6 +219,10 @@ PresentationService::Init()
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
rv = obs->AddObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC, false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
@ -219,6 +251,13 @@ PresentationService::Observe(nsISupports* aSubject,
}
return HandleSessionRequest(request);
} else if (!strcmp(aTopic, PRESENTATION_TERMINATE_REQUEST_TOPIC)) {
nsCOMPtr<nsIPresentationTerminateRequest> request(do_QueryInterface(aSubject));
if (NS_WARN_IF(!request)) {
return NS_ERROR_FAILURE;
}
return HandleTerminateRequest(request);
} else if (!strcmp(aTopic, "profile-after-change")) {
// It's expected since we add and entry to |kLayoutCategories| in
// |nsLayoutModule.cpp| to launch this service earlier.
@ -245,6 +284,7 @@ PresentationService::HandleShutdown()
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
obs->RemoveObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC);
}
}
@ -360,6 +400,58 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques
return NS_OK;
}
nsresult
PresentationService::HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest)
{
nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
return rv;
}
nsAutoString sessionId;
rv = aRequest->GetPresentationId(sessionId);
if (NS_WARN_IF(NS_FAILED(rv))) {
ctrlChannel->Disconnect(rv);
return rv;
}
nsCOMPtr<nsIPresentationDevice> device;
rv = aRequest->GetDevice(getter_AddRefs(device));
if (NS_WARN_IF(NS_FAILED(rv))) {
ctrlChannel->Disconnect(rv);
return rv;
}
bool isFromReceiver;
rv = aRequest->GetIsFromReceiver(&isFromReceiver);
if (NS_WARN_IF(NS_FAILED(rv))) {
ctrlChannel->Disconnect(rv);
return rv;
}
RefPtr<PresentationSessionInfo> info;
if (!isFromReceiver) {
info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER);
} else {
info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_CONTROLLER);
}
if (NS_WARN_IF(!info)) {
// Cannot terminate non-existed session.
ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR);
return NS_ERROR_DOM_ABORT_ERR;
}
// Check if terminate request comes from known device.
RefPtr<nsIPresentationDevice> knownDevice = info->GetDevice();
if (NS_WARN_IF(!IsSameDevice(device, knownDevice))) {
ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR);
return NS_ERROR_DOM_ABORT_ERR;
}
return info->OnTerminate(ctrlChannel);
}
void
PresentationService::NotifyAvailableChange(bool aIsAvailable)
{
@ -525,7 +617,6 @@ PresentationService::CloseSession(const nsAString& aSessionId,
}
if (aClosedReason == nsIPresentationService::CLOSED_REASON_WENTAWAY) {
UntrackSessionInfo(aSessionId, aRole);
// Remove nsIPresentationSessionListener since we don't want to dispatch
// PresentationConnectionClosedEvent if the page is went away.
info->SetListener(nullptr);
@ -611,8 +702,9 @@ PresentationService::UnregisterSessionListener(const nsAString& aSessionId,
RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
if (info) {
NS_WARN_IF(NS_FAILED(info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED)));
UntrackSessionInfo(aSessionId, aRole);
// When content side decide not handling this session anymore, simply
// close the connection. Session info is kept for reconnection.
NS_WARN_IF(NS_FAILED(info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED)));
return info->SetListener(nullptr);
}
return NS_OK;
@ -729,6 +821,17 @@ PresentationService::UntrackSessionInfo(const nsAString& aSessionId,
if (nsIPresentationService::ROLE_CONTROLLER == aRole) {
mSessionInfoAtController.Remove(aSessionId);
} else {
// Terminate receiver page.
uint64_t windowId;
nsresult rv = GetWindowIdBySessionIdInternal(aSessionId, &windowId);
if (NS_SUCCEEDED(rv)) {
NS_DispatchToMainThread(NS_NewRunnableFunction([windowId]() -> void {
if (auto* window = nsGlobalWindow::GetInnerWindowWithId(windowId)) {
window->Close();
}
}));
}
mSessionInfoAtReceiver.Remove(aSessionId);
}

Просмотреть файл

@ -14,6 +14,7 @@
#include "PresentationSessionInfo.h"
class nsIPresentationSessionRequest;
class nsIPresentationTerminateRequest;
class nsIURI;
class nsIPresentationSessionTransportBuilder;
@ -66,6 +67,7 @@ private:
void HandleShutdown();
nsresult HandleDeviceChange();
nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
nsresult HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest);
void NotifyAvailableChange(bool aIsAvailable);
bool IsAppInstalled(nsIURI* aUri);

Просмотреть файл

@ -236,6 +236,7 @@ PresentationSessionInfo::Shutdown(nsresult aReason)
}
mIsResponderReady = false;
mIsOnTerminating = false;
SetBuilder(nullptr);
}
@ -284,9 +285,42 @@ PresentationSessionInfo::Close(nsresult aReason,
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
// Do nothing if session is already terminated.
if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
return NS_OK;
}
SetStateWithReason(aState, aReason);
Shutdown(aReason);
switch (aState) {
case nsIPresentationSessionListener::STATE_CLOSED: {
Shutdown(aReason);
break;
}
case nsIPresentationSessionListener::STATE_TERMINATED: {
if (!mControlChannel) {
nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
nsresult rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
if (NS_SUCCEEDED(rv)) {
SetControlChannel(ctrlChannel);
}
return rv;
}
return mControlChannel->Terminate(mSessionId);
}
}
return NS_OK;
}
nsresult
PresentationSessionInfo::OnTerminate(nsIPresentationControlChannel* aControlChannel)
{
mIsOnTerminating = true; // Mark for terminating transport channel
SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, NS_OK);
SetControlChannel(aControlChannel);
return NS_OK;
}
@ -342,6 +376,18 @@ PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId)
return true;
}
void
PresentationSessionInfo::ContinueTermination()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mControlChannel);
if (NS_WARN_IF(NS_FAILED(mControlChannel->Terminate(mSessionId)))
|| mIsOnTerminating) {
Shutdown(NS_OK);
}
}
// nsIPresentationSessionTransportCallback
NS_IMETHODIMP
PresentationSessionInfo::NotifyTransportReady()
@ -692,6 +738,27 @@ PresentationControllingInfo::NotifyConnected()
{
MOZ_ASSERT(NS_IsMainThread());
switch (mState) {
case nsIPresentationSessionListener::STATE_CONNECTING: {
Unused << NS_WARN_IF(NS_FAILED(BuildTransport()));
break;
}
case nsIPresentationSessionListener::STATE_TERMINATED: {
ContinueTermination();
break;
}
default:
break;
}
return NS_OK;
}
nsresult
PresentationControllingInfo::BuildTransport()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl());
if (NS_FAILED(rv)) {
return rv;
@ -1139,7 +1206,10 @@ PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
NS_IMETHODIMP
PresentationPresentingInfo::NotifyConnected()
{
// Do nothing.
if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
ContinueTermination();
}
return NS_OK;
}

Просмотреть файл

@ -105,6 +105,8 @@ public:
nsresult Close(nsresult aReason,
uint32_t aState);
nsresult OnTerminate(nsIPresentationControlChannel* aControlChannel);
nsresult ReplyError(nsresult aReason);
virtual bool IsAccessible(base::ProcessId aProcessId);
@ -142,6 +144,8 @@ protected:
}
}
void ContinueTermination();
// Should be nsIPresentationChannelDescription::TYPE_TCP/TYPE_DATACHANNEL
uint8_t mTransportType = 0;
@ -154,6 +158,7 @@ protected:
uint8_t mRole;
bool mIsResponderReady;
bool mIsTransportReady;
bool mIsOnTerminating = false;
uint32_t mState; // CONNECTED, CLOSED, TERMINATED
nsresult mReason;
nsCOMPtr<nsIPresentationSessionListener> mListener;
@ -193,6 +198,8 @@ private:
nsresult OnGetAddress(const nsACString& aAddress);
nsresult BuildTransport();
nsCOMPtr<nsIServerSocket> mServerSocket;
};

Просмотреть файл

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PresentationTerminateRequest.h"
#include "nsIPresentationControlChannel.h"
#include "nsIPresentationDevice.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(PresentationTerminateRequest, nsIPresentationTerminateRequest)
PresentationTerminateRequest::PresentationTerminateRequest(
nsIPresentationDevice* aDevice,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
: mPresentationId(aPresentationId)
, mDevice(aDevice)
, mControlChannel(aControlChannel)
, mIsFromReceiver(aIsFromReceiver)
{
}
PresentationTerminateRequest::~PresentationTerminateRequest()
{
}
// nsIPresentationTerminateRequest
NS_IMETHODIMP
PresentationTerminateRequest::GetDevice(nsIPresentationDevice** aRetVal)
{
NS_ENSURE_ARG_POINTER(aRetVal);
nsCOMPtr<nsIPresentationDevice> device = mDevice;
device.forget(aRetVal);
return NS_OK;
}
NS_IMETHODIMP
PresentationTerminateRequest::GetPresentationId(nsAString& aRetVal)
{
aRetVal = mPresentationId;
return NS_OK;
}
NS_IMETHODIMP
PresentationTerminateRequest::GetControlChannel(
nsIPresentationControlChannel** aRetVal)
{
NS_ENSURE_ARG_POINTER(aRetVal);
nsCOMPtr<nsIPresentationControlChannel> controlChannel = mControlChannel;
controlChannel.forget(aRetVal);
return NS_OK;
}
NS_IMETHODIMP
PresentationTerminateRequest::GetIsFromReceiver(bool* aRetVal)
{
*aRetVal = mIsFromReceiver;
return NS_OK;
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_PresentationTerminateRequest_h__
#define mozilla_dom_PresentationTerminateRequest_h__
#include "nsIPresentationTerminateRequest.h"
#include "nsCOMPtr.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
class PresentationTerminateRequest final : public nsIPresentationTerminateRequest
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPRESENTATIONTERMINATEREQUEST
PresentationTerminateRequest(nsIPresentationDevice* aDevice,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver);
private:
virtual ~PresentationTerminateRequest();
nsString mPresentationId;
nsCOMPtr<nsIPresentationDevice> mDevice;
nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
bool mIsFromReceiver;
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_PresentationTerminateRequest_h__ */

Просмотреть файл

@ -18,6 +18,7 @@ XPIDL_SOURCES += [
'nsIPresentationSessionRequest.idl',
'nsIPresentationSessionTransport.idl',
'nsIPresentationSessionTransportBuilder.idl',
'nsIPresentationTerminateRequest.idl',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':

Просмотреть файл

@ -110,6 +110,13 @@ interface nsIPresentationControlChannel: nsISupports
*/
void launch(in DOMString presentationId, in DOMString url);
/*
* Terminate a presentation on remote endpoint.
* @param presentationId The Id for representing this session.
* @throws NS_ERROR_FAILURE on failure
*/
void terminate(in DOMString presentationId);
/*
* Disconnect the control channel.
* @param reason The reason of disconnecting channel; NS_OK represents normal.

Просмотреть файл

@ -45,6 +45,18 @@ interface nsIPresentationControlServerListener: nsISupports
in DOMString aUrl,
in DOMString aPresentationId,
in nsIPresentationControlChannel aControlChannel);
/**
* Callback while the remote host is requesting to terminate a presentation session.
* @param aDeviceInfo The device information related to the remote host.
* @param aPresentationId The Id for representing this session.
* @param aControlChannel The control channel for this session.
* @param aIsFromReceiver true if termination is initiated by receiver.
*/
void onTerminateRequest(in nsITCPDeviceInfo aDeviceInfo,
in DOMString aPresentationId,
in nsIPresentationControlChannel aControlChannel,
in boolean aIsFromReceiver);
};
/**

Просмотреть файл

@ -32,6 +32,18 @@ interface nsIPresentationDeviceListener: nsISupports
in DOMString url,
in DOMString presentationId,
in nsIPresentationControlChannel controlChannel);
/*
* Callback while the remote device is requesting to terminate a presentation session.
* @param device The remote device that sent session request.
* @param presentationId The Id for representing this session.
* @param controlChannel The control channel for this session.
* @param aIsFromReceiver true if termination is initiated by receiver.
*/
void onTerminateRequest(in nsIPresentationDevice device,
in DOMString presentationId,
in nsIPresentationControlChannel controlChannel,
in boolean aIsFromReceiver);
};
/*

Просмотреть файл

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIPresentationDevice;
interface nsIPresentationControlChannel;
%{C++
#define PRESENTATION_TERMINATE_REQUEST_TOPIC "presentation-terminate-request"
%}
/*
* The event of a device requesting for terminating a presentation session. User can
* monitor the terminate request on every device by observing "presentation-terminate-request".
*/
[scriptable, uuid(3ddbf3a4-53ee-4b70-9bbc-58ac90dce6b5)]
interface nsIPresentationTerminateRequest: nsISupports
{
// The device which requesting to terminate presentation session.
readonly attribute nsIPresentationDevice device;
// The Id for representing this session.
readonly attribute DOMString presentationId;
// The control channel for this session.
// Should only use this channel to complete session termination.
readonly attribute nsIPresentationControlChannel controlChannel;
// True if termination is initiated by receiver.
readonly attribute boolean isFromReceiver;
};

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/dom/PPresentation.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "nsGlobalWindow.h"
#include "nsIPresentationListener.h"
#include "PresentationCallbacks.h"
#include "PresentationChild.h"
@ -341,6 +342,18 @@ NS_IMETHODIMP
PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId,
uint8_t aRole)
{
if (nsIPresentationService::ROLE_RECEIVER == aRole) {
// Terminate receiver page.
uint64_t windowId;
if (NS_SUCCEEDED(GetWindowIdBySessionIdInternal(aSessionId, &windowId))) {
NS_DispatchToMainThread(NS_NewRunnableFunction([windowId]() -> void {
if (auto* window = nsGlobalWindow::GetInnerWindowWithId(windowId)) {
window->Close();
}
}));
}
}
// Remove the OOP responding info (if it has never been used).
RemoveRespondingSessionId(aSessionId);
if (mSessionInfos.Contains(aSessionId)) {

Просмотреть файл

@ -52,6 +52,7 @@ UNIFIED_SOURCES += [
'PresentationSessionInfo.cpp',
'PresentationSessionRequest.cpp',
'PresentationTCPSessionTransport.cpp',
'PresentationTerminateRequest.cpp',
]
EXTRA_COMPONENTS += [

Просмотреть файл

@ -50,6 +50,12 @@ var handlers = [
case CommandType.LAUNCH_ACK:
stateMachine._notifyLaunch(command.presentationId);
break;
case CommandType.TERMINATE:
stateMachine._notifyTerminate(command.presentationId);
break;
case CommandType.TERMINATE_ACK:
stateMachine._notifyTerminate(command.presentationId);
break;
case CommandType.ANSWER:
case CommandType.ICE_CANDIDATE:
stateMachine._notifyChannelDescriptor(command);
@ -87,6 +93,24 @@ ControllerStateMachine.prototype = {
}
},
terminate: function _terminate(presentationId) {
if (this.state === State.CONNECTED) {
this._sendCommand({
type: CommandType.TERMINATE,
presentationId: presentationId,
});
}
},
terminateAck: function _terminateAck(presentationId) {
if (this.state === State.CONNECTED) {
this._sendCommand({
type: CommandType.TERMINATE_ACK,
presentationId: presentationId,
});
}
},
sendOffer: function _sendOffer(offer) {
if (this.state === State.CONNECTED) {
this._sendCommand({
@ -172,6 +196,10 @@ ControllerStateMachine.prototype = {
this._channel.notifyLaunch(presentationId);
},
_notifyTerminate: function _notifyTerminate(presentationId) {
this._channel.notifyTerminate(presentationId);
},
_notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
switch (command.type) {
case CommandType.ANSWER:

Просмотреть файл

@ -405,6 +405,37 @@ DisplayDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDeviceInfo);
MOZ_ASSERT(aControlChannel);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!listener);
rv = listener->OnTerminateRequest(mDevice,
aPresentationId,
aControlChannel,
aIsFromReceiver);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
DisplayDeviceProvider::Observe(nsISupports* aSubject,

Просмотреть файл

@ -231,6 +231,12 @@ LegacyTCPControlChannel.prototype = {
this._sendInit();
},
terminate: function() {
// Legacy protocol doesn't support extra terminate protocol.
// Trigger error handling for browser to shutdown all the resource locally.
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
sendOffer: function(aOffer) {
let msg = {
type: "requestSession:Offer",

Просмотреть файл

@ -917,6 +917,50 @@ MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
return NS_OK;
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnTerminateRequest: %s", address.get());
RefPtr<Device> device;
uint32_t index;
if (FindDeviceByAddress(address, index)) {
device = mDevices[index];
} else {
// Create a one-time device object for non-discoverable controller.
// This device will not be in the list of available devices and cannot
// be used for requesting session.
nsAutoCString id;
Unused << aDeviceInfo->GetId(id);
uint16_t port;
Unused << aDeviceInfo->GetPort(&port);
device = new Device(id,
/* aName = */ id,
/* aType = */ EmptyCString(),
address,
port,
DeviceState::eActive,
/* aProvider = */ nullptr);
}
nsCOMPtr<nsIPresentationDeviceListener> listener;
if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
Unused << listener->OnTerminateRequest(device, aPresentationId,
aControlChannel, aIsFromReceiver);
}
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
MulticastDNSDeviceProvider::Observe(nsISupports* aSubject,

Просмотреть файл

@ -166,6 +166,11 @@ PresentationControlService.prototype = {
onSessionRequest: function(aDeviceInfo, aUrl, aPresentationId, aControlChannel) {
DEBUG && log("PresentationControlService - onSessionRequest: " +
aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
if (!this.listener) {
this.releaseControlChannel(aControlChannel);
return;
}
this.listener.onSessionRequest(aDeviceInfo,
aUrl,
aPresentationId,
@ -173,6 +178,21 @@ PresentationControlService.prototype = {
this.releaseControlChannel(aControlChannel);
},
onSessionTerminate: function(aDeviceInfo, aPresentationId, aControlChannel, aIsFromReceiver) {
DEBUG && log("TCPPresentationServer - onSessionTerminate: " +
aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
if (!this.listener) {
this.releaseControlChannel(aControlChannel);
return;
}
this.listener.onTerminateRequest(aDeviceInfo,
aPresentationId,
aControlChannel,
aIsFromReceiver);
this.releaseControlChannel(aControlChannel);
},
// nsIServerSocketListener (Triggered by nsIServerSocket.init)
onSocketAccepted: function(aServerSocket, aClientSocket) {
DEBUG && log("PresentationControlService - onSocketAccepted: " +
@ -390,6 +410,16 @@ TCPControlChannel.prototype = {
this._stateMachine.launch(aPresentationId, aUrl);
},
terminate: function(aPresentationId) {
if (!this._terminatingId) {
this._terminatingId = aPresentationId;
this._stateMachine.terminate(aPresentationId);
} else {
this._stateMachine.terminateAck(aPresentationId);
delete this._terminatingId;
}
},
// may throw an exception
_send: function(aMsg) {
DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); // jshint ignore:line
@ -650,6 +680,26 @@ TCPControlChannel.prototype = {
}
},
notifyTerminate: function(presentationId) {
if (!this._terminatingId) {
this._terminatingId = presentationId;
this._presentationService.onSessionTerminate(this._deviceInfo,
presentationId,
this,
this._direction === "sender");
return;
}
if (this._terminatingId !== presentationId) {
// Requested presentation Id doesn't matched with the one in ACK.
// Disconnect the control channel with error.
DEBUG && log("TCPControlChannel - unmatched terminatingId: " + presentationId); // jshint ignore:line
this.disconnect(Cr.NS_ERROR_FAILURE);
}
delete this._terminatingId;
},
notifyOffer: function(offer) {
this._onOffer(offer);
},

Просмотреть файл

@ -58,6 +58,12 @@ var handlers = [
presentationId: command.presentationId
});
break;
case CommandType.TERMINATE:
stateMachine._notifyTerminate(command.presentationId);
break;
case CommandType.TERMINATE_ACK:
stateMachine._notifyTerminate(command.presentationId);
break;
case CommandType.OFFER:
case CommandType.ICE_CANDIDATE:
stateMachine._notifyChannelDescriptor(command);
@ -89,6 +95,24 @@ ReceiverStateMachine.prototype = {
debug("receiver shouldn't trigger launch");
},
terminate: function _terminate(presentationId) {
if (this.state === State.CONNECTED) {
this._sendCommand({
type: CommandType.TERMINATE,
presentationId: presentationId,
});
}
},
terminateAck: function _terminateAck(presentationId) {
if (this.state === State.CONNECTED) {
this._sendCommand({
type: CommandType.TERMINATE_ACK,
presentationId: presentationId,
});
}
},
sendOffer: function _sendOffer() {
// offer can only be sent by controlling UA.
debug("receiver shouldn't generate offer");
@ -171,6 +195,10 @@ ReceiverStateMachine.prototype = {
this._channel.notifyLaunch(presentationId, url);
},
_notifyTerminate: function _notifyTerminate(presentationId) {
this._channel.notifyTerminate(presentationId);
},
_notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
switch (command.type) {
case CommandType.OFFER:

Просмотреть файл

@ -25,6 +25,8 @@ const CommandType = Object.freeze({
// presentation session life cycle
LAUNCH: "launch", // { presentationId: <string>, url: <string> }
LAUNCH_ACK: "launch-ack", // { presentationId: <string> }
TERMINATE: "terminate", // { presentationId: <string> }
TERMINATE_ACK: "terminate-ack", // { presentationId: <string> }
// session transport establishment
OFFER: "offer", // { offer: <json> }
ANSWER: "answer", // { answer: <json> }

Просмотреть файл

@ -45,7 +45,7 @@ function registerOriginalFactory(contractId, mockedClassId, mockedFactory, origi
}
}
const sessionId = 'test-session-id-' + uuidGenerator.generateUUID().toString();
var sessionId = 'test-session-id-' + uuidGenerator.generateUUID().toString();
const address = Cc["@mozilla.org/supports-cstring;1"]
.createInstance(Ci.nsISupportsCString);
@ -142,6 +142,10 @@ const mockedControlChannel = {
return isValid;
},
launch: function(presentationId, url) {
sessionId = presentationId;
},
terminate: function(presentationId) {
sendAsyncMessage('sender-terminate', presentationId);
},
disconnect: function(reason) {
sendAsyncMessage('control-channel-closed', reason);
@ -392,6 +396,13 @@ addMessageListener('trigger-incoming-session-request', function(url) {
.onSessionRequest(mockedDevice, url, sessionId, mockedControlChannel);
});
addMessageListener('trigger-incoming-terminate-request', function() {
var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
.getService(Ci.nsIPresentationDeviceManager);
deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
.onTerminateRequest(mockedDevice, sessionId, mockedControlChannel, true);
});
addMessageListener('trigger-incoming-offer', function() {
mockedControlChannel.simulateOnOffer();
});

Просмотреть файл

@ -17,7 +17,7 @@ function debug(str) {
}
const originalFactoryData = [];
const sessionId = 'test-session-id-' + uuidGenerator.generateUUID().toString();
var sessionId; // Store the uuid generated by PresentationRequest.
const address = Cc["@mozilla.org/supports-cstring;1"]
.createInstance(Ci.nsISupportsCString);
address.data = "127.0.0.1";
@ -184,14 +184,21 @@ const mockControlChannelOfSender = {
.onAnswer(answer);
},
launch: function(presentationId, url) {
sessionId = presentationId;
sendAsyncMessage('sender-launch', url);
},
disconnect: function(reason) {
if (!this._listener) {
return;
}
this._listener
.QueryInterface(Ci.nsIPresentationControlChannelListener)
.notifyDisconnected(reason);
mockControlChannelOfReceiver.disconnect();
}
},
terminate: function(presentationId) {
sendAsyncMessage('sender-terminate');
},
};
// control channel of receiver
@ -236,10 +243,16 @@ const mockControlChannelOfReceiver = {
sendAsyncMessage('answer-sent');
},
disconnect: function(reason) {
if (!this._listener) {
return;
}
this._listener
.QueryInterface(Ci.nsIPresentationControlChannelListener)
.notifyDisconnected(reason);
sendAsyncMessage('control-channel-receiver-closed', reason);
},
terminate: function(presentaionId) {
}
};
@ -367,6 +380,17 @@ function initMockAndListener() {
mockControlChannelOfReceiver);
});
addMessageListener('trigger-on-terminate-request', function() {
debug('Got message: trigger-on-terminate-request');
var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
.getService(Ci.nsIPresentationDeviceManager);
deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
.onTerminateRequest(mockDevice,
sessionId,
mockControlChannelOfReceiver,
false);
});
addMessageListener('trigger-control-channel-open', function(reason) {
debug('Got message: trigger-control-channel-open');
mockControlChannelOfSender.notifyConnected();

Просмотреть файл

@ -111,6 +111,7 @@ function testConnectionClosed() {
connection.onclose = function() {
connection.onclose = null;
is(connection.state, "closed", "Receiver: Connection should be closed.");
command('forward-command', JSON.stringify({ name: 'receiver-closed' }));
aResolve();
};
command('forward-command', JSON.stringify({ name: 'ready-to-close' }));
@ -122,8 +123,7 @@ function runTests() {
.then(testConnectionReady)
.then(testIncomingMessage)
.then(testSendMessage)
.then(testConnectionClosed)
.then(finish);
.then(testConnectionClosed);
}
runTests();

Просмотреть файл

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
<html>
<head>
<meta charset='utf-8'>
<title>Test for B2G PresentationReceiver at receiver side</title>
</head>
<body>
<div id='content'></div>
<script type='application/javascript;version=1.7'>
'use strict';
function is(a, b, msg) {
if (a === b) {
alert('OK ' + msg);
} else {
alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
}
}
function ok(a, msg) {
alert((a ? 'OK ' : 'KO ') + msg);
}
function info(msg) {
alert('INFO ' + msg);
}
function command(name, data) {
alert('COMMAND ' + JSON.stringify({name: name, data: data}));
}
function finish() {
alert('DONE');
}
var connection;
function testConnectionAvailable() {
return new Promise(function(aResolve, aReject) {
info('Receiver: --- testConnectionAvailable ---');
ok(navigator.presentation, 'Receiver: navigator.presentation should be available.');
ok(navigator.presentation.receiver, 'Receiver: navigator.presentation.receiver should be available.');
navigator.presentation.receiver.connectionList
.then((aList) => {
is(aList.connections.length, 1, 'Should get one conncetion.');
connection = aList.connections[0];
ok(connection.id, 'Connection ID should be set: ' + connection.id);
is(connection.state, 'connected', 'Connection state at receiver side should be connected.');
aResolve();
})
.catch((aError) => {
ok(false, 'Receiver: Error occurred when getting the connection: ' + aError);
finish();
aReject();
});
});
}
function testConnectionReady() {
return new Promise(function(aResolve, aReject) {
info('Receiver: --- testConnectionReady ---');
connection.onconnect = function() {
connection.onconnect = null;
ok(false, 'Should not get |onconnect| event.')
aReject();
};
if (connection.state === 'connected') {
connection.onconnect = null;
is(connection.state, 'connected', 'Receiver: Connection state should become connected.');
aResolve();
}
});
}
function testConnectionTerminate() {
return new Promise(function(aResolve, aReject) {
info('Receiver: --- testConnectionTerminate ---');
connection.onterminate = function() {
connection.onterminate = null;
// Using window.alert at this stage will cause window.close() fail.
// Only trigger it if verdict fail.
if (connection.state !== 'terminated') {
is(connection.state, 'terminated', 'Receiver: Connection should be terminated.');
}
aResolve();
};
command('forward-command', JSON.stringify({ name: 'ready-to-terminate' }));
});
}
function runTests() {
testConnectionAvailable()
.then(testConnectionReady)
.then(testConnectionTerminate)
}
runTests();
</script>
</body>
</html>

Просмотреть файл

@ -15,6 +15,8 @@ support-files =
file_presentation_receiver_auxiliary_navigation.html
test_presentation_receiver_auxiliary_navigation.js
file_presentation_sandboxed_presentation.html
file_presentation_terminate.html
test_presentation_terminate.js
[test_presentation_dc_sender.html]
[test_presentation_dc_receiver.html]
@ -46,4 +48,10 @@ skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
skip-if = (e10s || toolkit == 'gonk')
[test_presentation_receiver_auxiliary_navigation_oop.html]
skip-if = (e10s || toolkit == 'gonk')
[test_presentation_terminate_inproc.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
[test_presentation_terminate_oop.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
[test_presentation_sender_on_terminate_request.html]
skip-if = toolkit == 'android'
[test_presentation_sandboxed_presentation.html]

Просмотреть файл

@ -1,6 +1,7 @@
'use strict';
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
function debug(str) {
// info(str);
@ -144,7 +145,11 @@ function testConnectionWentaway() {
connection.onclose = function() {
connection.onclose = null;
is(connection.state, "closed", "Sender: Connection should be closed.");
aResolve();
receiverIframe.addEventListener('mozbrowserclose', function closeHandler() {
ok(false, 'wentaway should not trigger receiver close');
aResolve();
});
setTimeout(aResolve, 3000);
};
gScript.addMessageListener('ready-to-remove-receiverFrame', function onReadyToRemove() {
gScript.removeMessageListener('ready-to-remove-receiverFrame', onReadyToRemove);

Просмотреть файл

@ -70,7 +70,6 @@ function setup() {
} else if (/^DONE$/.exec(message)) {
receiverIframe.removeEventListener("mozbrowsershowmodalprompt",
receiverListener);
teardown();
}
}, false);
@ -178,18 +177,55 @@ function testIncomingMessage() {
}
function testCloseConnection() {
return new Promise(function(aResolve, aReject) {
info('Sender: --- testCloseConnection ---');
connection.onclose = function() {
connection.onclose = null;
is(connection.state, "closed", "Sender: Connection should be closed.");
aResolve();
};
gScript.addMessageListener('ready-to-close', function onReadyToClose() {
gScript.removeMessageListener('ready-to-close', onReadyToClose);
connection.close();
info('Sender: --- testCloseConnection ---');
gScript.addMessageListener('ready-to-close', function onReadyToClose() {
gScript.removeMessageListener('ready-to-close', onReadyToClose);
connection.close();
// Test terminate immediate after close.
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established',
controlChannelEstablishedHandler);
ok(false, "terminate after close should do nothing");
});
connection.terminate();
});
return Promise.all([
new Promise(function(aResolve, aReject) {
connection.onclose = function() {
connection.onclose = null;
is(connection.state, 'closed', 'Sender: Connection should be closed.');
aResolve();
};
}),
new Promise(function(aResolve, aReject) {
gScript.addMessageListener('receiver-closed', function onReceiverClosed() {
gScript.removeMessageListener('receiver-closed', onReceiverClosed);
aResolve();
});
}),
]);
}
function testTerminateAfterClose() {
info('Sender: --- testTerminateAfterClose ---');
return Promise.race([
new Promise(function(aResolve, aReject) {
connection.onterminate = function() {
connection.onterminate = null;
ok(false, 'terminate after close should do nothing');
aResolve();
};
connection.terminate();
}),
new Promise(function(aResolve, aReject) {
setTimeout(function() {
is(connection.state, 'closed', 'Sender: Connection should be closed.');
aResolve();
}, 3000);
}),
]);
}
function teardown() {
@ -208,10 +244,13 @@ function runTests() {
.then(testStartConnection)
.then(testSendMessage)
.then(testIncomingMessage)
.then(testCloseConnection);
.then(testCloseConnection)
.then(testTerminateAfterClose)
.then(teardown);
}
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
SpecialPowers.pushPermissions([
{type: 'presentation-device-manage', allow: false, context: document},
{type: 'presentation', allow: true, context: document},

Просмотреть файл

@ -74,7 +74,6 @@ function setup() {
} else if (/^DONE$/.exec(message)) {
receiverIframe.removeEventListener("mozbrowsershowmodalprompt",
receiverListener);
teardown();
}
}, false);
@ -185,18 +184,55 @@ function testIncomingMessage() {
}
function testCloseConnection() {
return new Promise(function(aResolve, aReject) {
info('Sender: --- testCloseConnection ---');
connection.onclose = function() {
connection.onclose = null;
is(connection.state, "closed", "Sender: Connection should be closed.");
aResolve();
};
gScript.addMessageListener('ready-to-close', function onReadyToClose() {
gScript.removeMessageListener('ready-to-close', onReadyToClose);
connection.close();
info('Sender: --- testCloseConnection ---');
gScript.addMessageListener('ready-to-close', function onReadyToClose() {
gScript.removeMessageListener('ready-to-close', onReadyToClose);
connection.close();
// Test terminate immediate after close.
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established',
controlChannelEstablishedHandler);
ok(false, 'terminate after close should do nothing');
});
connection.terminate();
});
return Promise.all([
new Promise(function(aResolve, aReject) {
connection.onclose = function() {
connection.onclose = null;
is(connection.state, 'closed', 'Sender: Connection should be closed.');
aResolve();
};
}),
new Promise(function(aResolve, aReject) {
gScript.addMessageListener('receiver-closed', function onReceiverClosed() {
gScript.removeMessageListener('receiver-closed', onReceiverClosed);
aResolve();
});
}),
]);
}
function testTerminateAfterClose() {
info('Sender: --- testTerminateAfterClose ---');
return Promise.race([
new Promise(function(aResolve, aReject) {
connection.onterminate = function() {
connection.onterminate = null;
ok(false, 'terminate at closed state should do nothing');
aResolve();
};
connection.terminate();
}),
new Promise(function(aResolve, aReject) {
setTimeout(function() {
is(connection.state, 'closed', 'Sender: Connection should be closed.');
aResolve();
}, 3000);
}),
]);
}
function teardown() {
@ -215,10 +251,13 @@ function runTests() {
.then(testStartConnection)
.then(testSendMessage)
.then(testIncomingMessage)
.then(testCloseConnection);
.then(testCloseConnection)
.then(testTerminateAfterClose)
.then(teardown);
}
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
SpecialPowers.pushPermissions([
{type: 'presentation-device-manage', allow: false, context: document},
{type: 'presentation', allow: true, context: document},

Просмотреть файл

@ -0,0 +1,185 @@
<!DOCTYPE HTML>
<html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset="utf-8">
<title>Test onTerminateRequest at sender side</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276378">Test onTerminateRequest at sender side</a>
<script type="application/javascript;version=1.8">
'use strict';
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
var request;
var connection;
function testSetup() {
return new Promise(function(aResolve, aReject) {
request = new PresentationRequest("http://example.com");
request.getAvailability().then(
function(aAvailability) {
aAvailability.onchange = function() {
aAvailability.onchange = null;
ok(aAvailability.value, "Device should be available.");
aResolve();
}
},
function(aError) {
ok(false, "Error occurred when getting availability: " + aError);
teardown();
aReject();
}
);
gScript.sendAsyncMessage('trigger-device-add');
});
}
function testStartConnection() {
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('answer-received', function answerReceivedHandler() {
gScript.removeMessageListener('answer-received', answerReceivedHandler);
info("An answer is received.");
});
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-incoming-answer');
});
gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
info("Data notification is enabled for data transport channel.");
});
var connectionFromEvent;
request.onconnectionavailable = function(aEvent) {
request.onconnectionavailable = null;
connectionFromEvent = aEvent.connection;
ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
if (connection) {
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
}
};
request.start().then(
function(aConnection) {
connection = aConnection;
ok(connection, "Connection should be available.");
ok(connection.id, "Connection ID should be set.");
is(connection.state, "connecting", "The initial state should be connecting.");
if (connectionFromEvent) {
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
}
connection.onconnect = function() {
connection.onconnect = null;
is(connection.state, "connected", "Connection should be connected.");
aResolve();
};
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
}
function testOnTerminateRequest() {
return new Promise(function(aResolve, aReject) {
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('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
});
connection.onterminate = function() {
connection.onterminate = null;
is(connection.state, "terminated", "Connection should be closed.");
aResolve();
};
gScript.sendAsyncMessage('trigger-incoming-terminate-request');
gScript.sendAsyncMessage('trigger-control-channel-open');
});
}
function teardown() {
gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
gScript.destroy();
SimpleTest.finish();
});
gScript.sendAsyncMessage('teardown');
}
function runTests() {
ok(window.PresentationRequest, "PresentationRequest should be available.");
testSetup().
then(testStartConnection).
then(testOnTerminateRequest).
then(teardown);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPermissions([
{type: 'presentation-device-manage', allow: false, context: document},
{type: 'presentation', allow: true, context: document},
], function() {
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
["dom.presentation.session_transport.data_channel.enable", false]]},
runTests);
});
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,241 @@
'use strict';
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
function debug(str) {
// info(str);
}
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
var receiverUrl = SimpleTest.getTestFileURL('file_presentation_terminate.html');
var request;
var connection;
var receiverIframe;
function setup() {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
debug('Got message: device-prompt');
gScript.removeMessageListener('device-prompt', devicePromptHandler);
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established',
controlChannelEstablishedHandler);
gScript.sendAsyncMessage('trigger-control-channel-open');
});
gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
debug('Got message: sender-launch');
gScript.removeMessageListener('sender-launch', senderLaunchHandler);
is(url, receiverUrl, 'Receiver: should receive the same url');
receiverIframe = document.createElement('iframe');
receiverIframe.setAttribute('mozbrowser', 'true');
receiverIframe.setAttribute('mozpresentation', receiverUrl);
var oop = location.pathname.indexOf('_inproc') == -1;
receiverIframe.setAttribute('remote', oop);
receiverIframe.setAttribute('src', receiverUrl);
receiverIframe.addEventListener('mozbrowserloadend', function mozbrowserloadendHander() {
receiverIframe.removeEventListener('mozbrowserloadend', mozbrowserloadendHander);
info('Receiver loaded.');
});
// This event is triggered when the iframe calls 'alert'.
receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(evt) {
var message = evt.detail.message;
if (/^OK /.exec(message)) {
ok(true, message.replace(/^OK /, ''));
} else if (/^KO /.exec(message)) {
ok(false, message.replace(/^KO /, ''));
} else if (/^INFO /.exec(message)) {
info(message.replace(/^INFO /, ''));
} else if (/^COMMAND /.exec(message)) {
var command = JSON.parse(message.replace(/^COMMAND /, ''));
gScript.sendAsyncMessage(command.name, command.data);
} else if (/^DONE$/.exec(message)) {
ok(true, 'Messaging from iframe complete.');
receiverIframe.removeEventListener('mozbrowsershowmodalprompt',
receiverListener);
}
}, false);
var promise = new Promise(function(aResolve, aReject) {
document.body.appendChild(receiverIframe);
aResolve(receiverIframe);
});
var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
.getService(SpecialPowers.Ci.nsIObserverService);
obs.notifyObservers(promise, 'setup-request-promise', null);
});
gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
debug('Got message: promise-setup-ready');
gScript.removeMessageListener('promise-setup-ready',
promiseSetupReadyHandler);
gScript.sendAsyncMessage('trigger-on-session-request', receiverUrl);
});
gScript.addMessageListener('offer-sent', function offerSentHandler() {
debug('Got message: offer-sent');
gScript.removeMessageListener('offer-sent', offerSentHandler);
gScript.sendAsyncMessage('trigger-on-offer');
});
gScript.addMessageListener('answer-sent', function answerSentHandler() {
debug('Got message: answer-sent');
gScript.removeMessageListener('answer-sent', answerSentHandler);
gScript.sendAsyncMessage('trigger-on-answer');
});
return Promise.resolve();
}
function testCreateRequest() {
return new Promise(function(aResolve, aReject) {
info('Sender: --- testCreateRequest ---');
request = new PresentationRequest(receiverUrl);
request.getAvailability().then((aAvailability) => {
aAvailability.onchange = function() {
aAvailability.onchange = null;
ok(aAvailability.value, 'Sender: Device should be available.');
aResolve();
}
}).catch((aError) => {
ok(false, 'Sender: Error occurred when getting availability: ' + aError);
teardown();
aReject();
});
gScript.sendAsyncMessage('trigger-device-add');
});
}
function testStartConnection() {
return new Promise(function(aResolve, aReject) {
request.start().then((aConnection) => {
connection = aConnection;
ok(connection, 'Sender: Connection should be available.');
ok(connection.id, 'Sender: Connection ID should be set.');
is(connection.state, 'connecting', 'Sender: The initial state should be connecting.');
connection.onconnect = function() {
connection.onconnect = null;
is(connection.state, 'connected', 'Connection should be connected.');
aResolve();
};
info('Sender: test terminate at connecting state');
connection.onterminate = function() {
connection.onterminate = null;
ok(false, 'Should not be able to terminate at connecting state');
aReject();
}
connection.terminate();
}).catch((aError) => {
ok(false, 'Sender: Error occurred when establishing a connection: ' + aError);
teardown();
aReject();
});
});
}
function testConnectionTerminate() {
return new Promise(function(aResolve, aReject) {
info('Sender: --- testConnectionTerminate---');
connection.onterminate = function() {
connection.onterminate = null;
is(connection.state, 'terminated', 'Sender: Connection should be terminated.');
};
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established',
controlChannelEstablishedHandler);
gScript.sendAsyncMessage('trigger-control-channel-open');
});
gScript.addMessageListener('sender-terminate', function senderTerminateHandler() {
gScript.removeMessageListener('sender-terminate',
senderTerminateHandler);
receiverIframe.addEventListener('mozbrowserclose', function() {
ok(true, 'observe receiver page closing');
aResolve();
});
gScript.sendAsyncMessage('trigger-on-terminate-request');
});
gScript.addMessageListener('ready-to-terminate', function onReadyToTerminate() {
gScript.removeMessageListener('ready-to-terminate', onReadyToTerminate);
connection.terminate();
// test unexpected close right after terminate
connection.onclose = function() {
ok(false, 'close after terminate should do nothing');
};
connection.close();
});
});
}
function testSendAfterTerminate() {
return new Promise(function(aResolve, aReject) {
try {
connection.send('something');
ok(false, 'PresentationConnection.send should be failed');
} catch (e) {
is(e.name, 'InvalidStateError', 'Must throw InvalidStateError');
}
aResolve();
});
}
function testCloseAfterTerminate() {
return Promise.race([
new Promise(function(aResolve, aReject) {
connection.onclose = function() {
connection.onclose = null;
ok(false, 'close at terminated state should do nothing');
aResolve();
};
connection.close();
}),
new Promise(function(aResolve, aReject) {
setTimeout(function() {
is(connection.state, 'terminated', 'Sender: Connection should be terminated.');
aResolve();
}, 3000);
}),
]);
}
function teardown() {
gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
debug('Got message: teardown-complete');
gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
gScript.destroy();
SimpleTest.finish();
});
gScript.sendAsyncMessage('teardown');
}
function runTests() {
setup().then(testCreateRequest)
.then(testStartConnection)
.then(testConnectionTerminate)
.then(testSendAfterTerminate)
.then(testCloseAfterTerminate)
.then(teardown);
}
SpecialPowers.pushPermissions([
{type: 'presentation-device-manage', allow: false, context: document},
{type: 'presentation', allow: true, context: document},
{type: 'browser', allow: true, context: document},
], () => {
SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
['dom.presentation.test.enabled', true],
['dom.mozBrowserFramesEnabled', true],
['dom.ipc.tabs.disabled', false],
['dom.presentation.test.stage', 0]]},
runTests);
});

Просмотреть файл

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
<html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset='utf-8'>
<title>Test for PresentationConnection.terminate()</title>
<link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
<script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
</head>
<body>
<a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1276378'>
Test for PresentationConnection.terminate()</a>
<script type='application/javascript;version=1.8' src='test_presentation_terminate.js'>
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
<html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset='utf-8'>
<title>Test for PresentationConnection.terminate()</title>
<link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
<script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
</head>
<body>
<a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1276378'>
Test for PresentationConnection.terminate()</a>
<script type='application/javascript;version=1.8' src='test_presentation_terminate.js'>
</script>
</body>
</html>

Просмотреть файл

@ -23,6 +23,7 @@ TestPresentationControlChannel.prototype = {
sendAnswer: function(answer) {},
disconnect: function() {},
launch: function() {},
terminate: function() {},
set listener(listener) {},
get listener() {},
};
@ -139,6 +140,27 @@ function sessionRequest() {
.onSessionRequest(testDevice, testUrl, testPresentationId, testControlChannel);
}
function terminateRequest() {
let testUrl = 'http://www.example.org/';
let testPresentationId = 'test-presentation-id';
let testControlChannel = new TestPresentationControlChannel();
let testIsFromReceiver = true;
Services.obs.addObserver(function observer(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
let request = subject.QueryInterface(Ci.nsIPresentationTerminateRequest);
Assert.equal(request.device.id, testDevice.id, 'expected device');
Assert.equal(request.presentationId, testPresentationId, 'expected presentation Id');
Assert.equal(request.isFromReceiver, testIsFromReceiver, 'expected isFromReceiver');
run_next_test();
}, 'presentation-terminate-request', false);
manager.QueryInterface(Ci.nsIPresentationDeviceListener)
.onTerminateRequest(testDevice, testPresentationId,
testControlChannel, testIsFromReceiver);
}
function removeDevice() {
Services.obs.addObserver(function observer(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
@ -176,6 +198,7 @@ add_test(forceDiscovery);
add_test(addDevice);
add_test(updateDevice);
add_test(sessionRequest);
add_test(terminateRequest);
add_test(removeDevice);
add_test(removeProvider);

Просмотреть файл

@ -78,6 +78,45 @@ function launch() {
};
}
function terminateByController() {
Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
controllerState.terminate(testPresentationId);
mockReceiverChannel.notifyTerminate = function(presentationId) {
Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
Assert.equal(presentationId, testPresentationId, 'expected presentationId received');
mockControllerChannel.notifyTerminate = function(presentationId) {
Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack');
run_next_test();
};
receiverState.terminateAck(presentationId);
};
}
function terminateByReceiver() {
Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
receiverState.terminate(testPresentationId);
mockControllerChannel.notifyTerminate = function(presentationId) {
Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
Assert.equal(presentationId, testPresentationId, 'expected presentationId received');
mockReceiverChannel.notifyTerminate = function(presentationId) {
Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack');
run_next_test();
};
controllerState.terminateAck(presentationId);
};
}
function exchangeSDP() {
Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state');
Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state');
@ -185,6 +224,8 @@ function abnormalDisconnect() {
add_test(connect);
add_test(launch);
add_test(terminateByController);
add_test(terminateByReceiver);
add_test(exchangeSDP);
add_test(disconnect);
add_test(receiverDisconnect);

Просмотреть файл

@ -181,6 +181,175 @@ function testPresentationServer() {
};
}
function terminateRequest() {
let yayFuncs = makeJointSuccess(['controllerControlChannelConnected',
'controllerControlChannelDisconnected',
'presenterControlChannelDisconnected']);
let controllerControlChannel;
pcs.listener = {
onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiverj) {
controllerControlChannel = controlChannel;
Assert.equal(deviceInfo.id, pcs.id, 'expected device id');
Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
Assert.equal(isFromReceiver, false, 'expected request from controller');
controllerControlChannel.listener = {
notifyConnected: function() {
Assert.ok(true, 'control channel notify connected');
yayFuncs.controllerControlChannelConnected();
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify disconncted');
yayFuncs.controllerControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
};
let presenterDeviceInfo = {
id: 'presentatorID',
address: '127.0.0.1',
port: PRESENTER_CONTROL_CHANNEL_PORT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
};
let presenterControlChannel = pcs.connect(presenterDeviceInfo);
presenterControlChannel.listener = {
notifyConnected: function() {
presenterControlChannel.terminate('testPresentationId', 'http://example.com');
presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify disconnected');
yayFuncs.presenterControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
}
function terminateRequest() {
let yayFuncs = makeJointSuccess(['controllerControlChannelConnected',
'controllerControlChannelDisconnected',
'presenterControlChannelDisconnected',
'terminatedByController',
'terminatedByReceiver']);
let controllerControlChannel;
let terminatePhase = 'controller';
pcs.listener = {
onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiver) {
Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
controlChannel.terminate(presentationId); // Reply terminate ack.
if (terminatePhase === 'controller') {
controllerControlChannel = controlChannel;
Assert.equal(deviceInfo.id, pcs.id, 'expected controller device id');
Assert.equal(isFromReceiver, false, 'expected request from controller');
yayFuncs.terminatedByController();
controllerControlChannel.listener = {
notifyConnected: function() {
Assert.ok(true, 'control channel notify connected');
yayFuncs.controllerControlChannelConnected();
terminatePhase = 'receiver';
controllerControlChannel.terminate('testPresentationId');
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify disconncted');
yayFuncs.controllerControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
} else {
Assert.equal(deviceInfo.id, presenterDeviceInfo.id, 'expected presenter device id');
Assert.equal(isFromReceiver, true, 'expected request from receiver');
yayFuncs.terminatedByReceiver();
presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
};
let presenterDeviceInfo = {
id: 'presentatorID',
address: '127.0.0.1',
port: PRESENTER_CONTROL_CHANNEL_PORT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
};
let presenterControlChannel = pcs.connect(presenterDeviceInfo);
presenterControlChannel.listener = {
notifyConnected: function() {
presenterControlChannel.terminate('testPresentationId');
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify disconnected');
yayFuncs.presenterControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
}
function terminateRequestAbnormal() {
let yayFuncs = makeJointSuccess(['controllerControlChannelConnected',
'controllerControlChannelDisconnected',
'presenterControlChannelDisconnected']);
let controllerControlChannel;
pcs.listener = {
onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiver) {
Assert.equal(deviceInfo.id, pcs.id, 'expected controller device id');
Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
Assert.equal(isFromReceiver, false, 'expected request from controller');
controlChannel.terminate('unmatched-presentationId'); // Reply abnormal terminate ack.
controllerControlChannel = controlChannel;
controllerControlChannel.listener = {
notifyConnected: function() {
Assert.ok(true, 'control channel notify connected');
yayFuncs.controllerControlChannelConnected();
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, Cr.NS_ERROR_FAILURE, 'controllerControlChannel notify disconncted with error');
yayFuncs.controllerControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
};
let presenterDeviceInfo = {
id: 'presentatorID',
address: '127.0.0.1',
port: PRESENTER_CONTROL_CHANNEL_PORT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
};
let presenterControlChannel = pcs.connect(presenterDeviceInfo);
presenterControlChannel.listener = {
notifyConnected: function() {
presenterControlChannel.terminate('testPresentationId');
},
notifyDisconnected: function(aReason) {
Assert.equal(aReason, Cr.NS_ERROR_FAILURE, '4. presenterControlChannel notify disconnected with error');
yayFuncs.presenterControlChannelDisconnected();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
}
function setOffline() {
pcs.listener = {
onPortChange: function(aPort) {
@ -225,6 +394,8 @@ function changeCloseReason() {
}
add_test(loopOfferAnser);
add_test(terminateRequest);
add_test(terminateRequestAbnormal);
add_test(setOffline);
add_test(changeCloseReason);
add_test(oneMoreLoop);

Просмотреть файл

@ -72,6 +72,7 @@ var ecmaGlobals =
{name: "NaN", xbl: false},
"Number",
"Object",
"Promise",
"Proxy",
"RangeError",
"ReferenceError",
@ -961,8 +962,6 @@ var interfaceNamesInGlobalScope =
"ProcessingInstruction",
// IMPORTANT: Do not change this list without review from a DOM peer!
"ProgressEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"Promise",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "PushManager", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -922,18 +922,15 @@ private:
nsString mBase; // IsVoid() if we have no base URI string.
RefPtr<URLProxy> mBaseProxy;
ErrorResult& mRv;
RefPtr<URLProxy> mRetval;
public:
ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aURL, const Optional<nsAString>& aBase,
ErrorResult& aRv)
const nsAString& aURL, const Optional<nsAString>& aBase)
: WorkerMainThreadRunnable(aWorkerPrivate,
NS_LITERAL_CSTRING("URL :: Constructor"))
, mURL(aURL)
, mRv(aRv)
{
if (aBase.WasPassed()) {
mBase = aBase.Value();
@ -944,13 +941,11 @@ public:
}
ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aURL, URLProxy* aBaseProxy,
ErrorResult& aRv)
const nsAString& aURL, URLProxy* aBaseProxy)
: WorkerMainThreadRunnable(aWorkerPrivate,
NS_LITERAL_CSTRING("URL :: Constructor with BaseURL"))
, mURL(aURL)
, mBaseProxy(aBaseProxy)
, mRv(aRv)
{
mBase.SetIsVoid(true);
mWorkerPrivate->AssertIsOnWorkerThread();
@ -961,16 +956,18 @@ public:
{
AssertIsOnMainThread();
ErrorResult rv;
RefPtr<URLMainThread> url;
if (mBaseProxy) {
url = URLMainThread::Constructor(nullptr, mURL, mBaseProxy->URI(), mRv);
url = URLMainThread::Constructor(nullptr, mURL, mBaseProxy->URI(), rv);
} else if (!mBase.IsVoid()) {
url = URLMainThread::Constructor(nullptr, mURL, mBase, mRv);
url = URLMainThread::Constructor(nullptr, mURL, mBase, rv);
} else {
url = URLMainThread::Constructor(nullptr, mURL, nullptr, mRv);
url = URLMainThread::Constructor(nullptr, mURL, nullptr, rv);
}
if (mRv.Failed()) {
if (rv.Failed()) {
rv.SuppressException();
return true;
}
@ -979,8 +976,15 @@ public:
}
URLProxy*
GetURLProxy()
GetURLProxy(ErrorResult& aRv) const
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
if (!mRetval) {
aRv.ThrowTypeError<MSG_INVALID_URL>(mURL);
}
return mRetval;
}
};
@ -1211,9 +1215,8 @@ FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
return nullptr;
}
RefPtr<URLProxy> proxy = aRunnable->GetURLProxy();
if (NS_WARN_IF(!proxy)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
RefPtr<URLProxy> proxy = aRunnable->GetURLProxy(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -1232,7 +1235,7 @@ URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
URLWorker& base = static_cast<URLWorker&>(aBase);
RefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aURL, base.GetURLProxy(), aRv);
new ConstructorRunnable(workerPrivate, aURL, base.GetURLProxy());
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}
@ -1245,7 +1248,7 @@ URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
RefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aURL, aBase, aRv);
new ConstructorRunnable(workerPrivate, aURL, aBase);
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}
@ -1261,7 +1264,7 @@ URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
base = &aBase;
RefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aURL, base, aRv);
new ConstructorRunnable(workerPrivate, aURL, base);
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}

Просмотреть файл

@ -12,8 +12,8 @@
dictionary PositionOptions {
boolean enableHighAccuracy = false;
long timeout = 0x7fffffff;
long maximumAge = 0;
[Clamp] unsigned long timeout = 0x7fffffff;
[Clamp] unsigned long maximumAge = 0;
};
[NoInterfaceObject]

Просмотреть файл

@ -6,12 +6,14 @@
#include "ServiceWorkerRegistration.h"
#include "ipc/ErrorIPCUtils.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "nsCycleCollectionParticipant.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
@ -387,7 +389,7 @@ public:
class UpdateResultRunnable final : public WorkerRunnable
{
RefPtr<PromiseWorkerProxy> mPromiseProxy;
ErrorResult mStatus;
IPC::Message mSerializedErrorResult;
~UpdateResultRunnable()
{}
@ -396,30 +398,32 @@ public:
UpdateResultRunnable(PromiseWorkerProxy* aPromiseProxy, ErrorResult& aStatus)
: WorkerRunnable(aPromiseProxy->GetWorkerPrivate())
, mPromiseProxy(aPromiseProxy)
, mStatus(Move(aStatus))
{ }
{
// ErrorResult is not thread safe. Serialize it for transfer across
// threads.
IPC::WriteParam(&mSerializedErrorResult, aStatus);
aStatus.SuppressException();
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
// Deserialize the ErrorResult now that we are back in the worker
// thread.
ErrorResult status;
PickleIterator iter = PickleIterator(mSerializedErrorResult);
Unused << IPC::ReadParam(&mSerializedErrorResult, &iter, &status);
Promise* promise = mPromiseProxy->WorkerPromise();
if (mStatus.Failed()) {
promise->MaybeReject(mStatus);
if (status.Failed()) {
promise->MaybeReject(status);
} else {
promise->MaybeResolve(JS::UndefinedHandleValue);
}
mStatus.SuppressException();
status.SuppressException();
mPromiseProxy->CleanUp();
return true;
}
void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aSuccess) override
{
if (!aSuccess) {
mStatus.SuppressException();
}
}
};
class WorkerThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback

Просмотреть файл

@ -47,6 +47,7 @@ var ecmaGlobals =
"NaN",
"Number",
"Object",
"Promise",
"Proxy",
"RangeError",
"ReferenceError",
@ -174,8 +175,6 @@ var interfaceNamesInGlobalScope =
{ name: "PerformanceObserver", nightly: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "PerformanceObserverEntryList", nightly: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
"Promise",
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "PushEvent", b2g: false },
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -47,6 +47,7 @@ var ecmaGlobals =
"NaN",
"Number",
"Object",
"Promise",
"Proxy",
"RangeError",
"ReferenceError",
@ -166,8 +167,6 @@ var interfaceNamesInGlobalScope =
{ name: "PerformanceObserver", nightly: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "PerformanceObserverEntryList", nightly: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
"Promise",
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "PushManager", b2g: false },
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -240,7 +240,7 @@ nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElem
#endif
if (servoStyleSet) {
servoStyleSet->RestyleSubtree(child);
servoStyleSet->RestyleSubtree(child, /* aForce = */ true);
}
}
}

Просмотреть файл

@ -1138,6 +1138,10 @@ XMLHttpRequestMainThread::GetAllResponseHeaders(nsACString& aResponseHeaders,
return;
}
if (mErrorLoad) {
return;
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
RefPtr<nsHeaderVisitor> visitor =
new nsHeaderVisitor(*this, WrapNotNull(httpChannel));

Просмотреть файл

@ -40,6 +40,10 @@
#include "mozilla/Snprintf.h"
#include "nsIChannel.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "nsIHttpAuthenticatorCallback.h"
#include "mozilla/Mutex.h"
#include "nsICancelable.h"
//-----------------------------------------------------------------------------
@ -51,6 +55,7 @@ static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-n
static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi";
#define kNegotiateLen (sizeof(kNegotiate)-1)
#define DEFAULT_THREAD_TIMEOUT_MS 30000
//-----------------------------------------------------------------------------
@ -184,7 +189,260 @@ nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel,
}
NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator)
namespace {
//
// GetNextTokenCompleteEvent
//
// This event is fired on main thread when async call of
// nsHttpNegotiateAuth::GenerateCredentials is finished. During the Run()
// method the nsIHttpAuthenticatorCallback::OnCredsAvailable is called with
// obtained credentials, flags and NS_OK when successful, otherwise
// NS_ERROR_FAILURE is returned as a result of failed operation.
//
class GetNextTokenCompleteEvent final : public nsIRunnable,
public nsICancelable
{
virtual ~GetNextTokenCompleteEvent()
{
if (mCreds) {
free(mCreds);
}
};
public:
NS_DECL_THREADSAFE_ISUPPORTS
explicit GetNextTokenCompleteEvent(nsIHttpAuthenticatorCallback* aCallback)
: mCallback(aCallback)
, mCreds(nullptr)
, mCancelled(false)
{
}
NS_IMETHODIMP DispatchSuccess(char *aCreds,
uint32_t aFlags,
already_AddRefed<nsISupports> aSessionState,
already_AddRefed<nsISupports> aContinuationState)
{
// Called from worker thread
MOZ_ASSERT(!NS_IsMainThread());
mCreds = aCreds;
mFlags = aFlags;
mResult = NS_OK;
mSessionState = aSessionState;
mContinuationState = aContinuationState;
return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
NS_IMETHODIMP DispatchError(already_AddRefed<nsISupports> aSessionState,
already_AddRefed<nsISupports> aContinuationState)
{
// Called from worker thread
MOZ_ASSERT(!NS_IsMainThread());
mResult = NS_ERROR_FAILURE;
mSessionState = aSessionState;
mContinuationState = aContinuationState;
return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
NS_IMETHODIMP Run() override
{
// Runs on main thread
MOZ_ASSERT(NS_IsMainThread());
if (!mCancelled) {
nsCOMPtr<nsIHttpAuthenticatorCallback> callback;
callback.swap(mCallback);
callback->OnCredsGenerated(mCreds, mFlags, mResult, mSessionState, mContinuationState);
}
return NS_OK;
}
NS_IMETHODIMP Cancel(nsresult aReason) override
{
// Supposed to be called from main thread
MOZ_ASSERT(NS_IsMainThread());
mCancelled = true;
return NS_OK;
}
private:
nsCOMPtr<nsIHttpAuthenticatorCallback> mCallback;
char *mCreds; // This class owns it, freed in destructor
uint32_t mFlags;
nsresult mResult;
bool mCancelled;
nsCOMPtr<nsISupports> mSessionState;
nsCOMPtr<nsISupports> mContinuationState;
};
NS_IMPL_ISUPPORTS(GetNextTokenCompleteEvent, nsIRunnable, nsICancelable)
//
// GetNextTokenRunnable
//
// This runnable is created by GenerateCredentialsAsync and it runs
// in nsHttpNegotiateAuth::mNegotiateThread and calling GenerateCredentials.
//
class GetNextTokenRunnable final : public mozilla::Runnable
{
virtual ~GetNextTokenRunnable() {}
public:
GetNextTokenRunnable(nsIHttpAuthenticableChannel *authChannel,
const char *challenge,
bool isProxyAuth,
const char16_t *domain,
const char16_t *username,
const char16_t *password,
nsISupports *sessionState,
nsISupports *continuationState,
GetNextTokenCompleteEvent *aCompleteEvent
)
: mAuthChannel(authChannel)
, mChallenge(challenge)
, mIsProxyAuth(isProxyAuth)
, mDomain(domain)
, mUsername(username)
, mPassword(password)
, mSessionState(sessionState)
, mContinuationState(continuationState)
, mCompleteEvent(aCompleteEvent)
{
}
NS_IMETHODIMP Run() override
{
// Runs on worker thread
MOZ_ASSERT(!NS_IsMainThread());
char *creds;
uint32_t flags;
nsresult rv = ObtainCredentialsAndFlags(&creds, &flags);
// Passing session and continuation state this way to not touch
// referencing of the object that may not be thread safe.
// Not having a thread safe referencing doesn't mean the object
// cannot be used on multiple threads (one example is nsAuthSSPI.)
// This ensures state objects will be destroyed on the main thread
// when not changed by GenerateCredentials.
if (NS_FAILED(rv)) {
return mCompleteEvent->DispatchError(mSessionState.forget(),
mContinuationState.forget());
}
return mCompleteEvent->DispatchSuccess(creds, flags,
mSessionState.forget(),
mContinuationState.forget());
}
NS_IMETHODIMP ObtainCredentialsAndFlags(char **aCreds, uint32_t *aFlags)
{
nsresult rv;
// Use negotiate service to call GenerateCredentials outside of main thread
nsAutoCString contractId;
contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
contractId.Append("negotiate");
nsCOMPtr<nsIHttpAuthenticator> authenticator =
do_GetService(contractId.get(), &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsISupports *sessionState = mSessionState;
nsISupports *continuationState = mContinuationState;
// The continuationState is for the sake of completeness propagated
// to the caller (despite it is not changed in any GenerateCredentials
// implementation).
//
// The only implementation that use sessionState is the
// nsHttpDigestAuth::GenerateCredentials. Since there's no reason
// to implement nsHttpDigestAuth::GenerateCredentialsAsync
// because digest auth does not block the main thread, we won't
// propagate changes to sessionState to the caller because of
// the change is too complicated on the caller side.
//
// Should any of the session or continuation states change inside
// this method, they must be threadsafe.
rv = authenticator->GenerateCredentials(mAuthChannel,
mChallenge.get(),
mIsProxyAuth,
mDomain.get(),
mUsername.get(),
mPassword.get(),
&sessionState,
&continuationState,
aFlags,
aCreds);
if (mSessionState != sessionState) {
mSessionState = sessionState;
}
if (mContinuationState != continuationState) {
mContinuationState = continuationState;
}
return rv;
}
private:
nsCOMPtr<nsIHttpAuthenticableChannel> mAuthChannel;
nsCString mChallenge;
bool mIsProxyAuth;
nsString mDomain;
nsString mUsername;
nsString mPassword;
nsCOMPtr<nsISupports> mSessionState;
nsCOMPtr<nsISupports> mContinuationState;
RefPtr<GetNextTokenCompleteEvent> mCompleteEvent;
};
} // anonymous namespace
NS_IMETHODIMP
nsHttpNegotiateAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
nsIHttpAuthenticatorCallback* aCallback,
const char *challenge,
bool isProxyAuth,
const char16_t *domain,
const char16_t *username,
const char16_t *password,
nsISupports *sessionState,
nsISupports *continuationState,
nsICancelable **aCancelable)
{
NS_ENSURE_ARG(aCallback);
NS_ENSURE_ARG_POINTER(aCancelable);
RefPtr<GetNextTokenCompleteEvent> cancelEvent =
new GetNextTokenCompleteEvent(aCallback);
nsCOMPtr<nsIRunnable> getNextTokenRunnable =
new GetNextTokenRunnable(authChannel,
challenge,
isProxyAuth,
domain,
username,
password,
sessionState,
continuationState,
cancelEvent);
cancelEvent.forget(aCancelable);
nsresult rv;
if (!mNegotiateThread) {
mNegotiateThread =
new mozilla::LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
NS_LITERAL_CSTRING("NegotiateAuth"));
NS_ENSURE_TRUE(mNegotiateThread, NS_ERROR_OUT_OF_MEMORY);
}
rv = mNegotiateThread->Dispatch(getNextTokenRunnable, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
//
// GenerateCredentials
//

Просмотреть файл

@ -10,6 +10,7 @@
#include "nsIURI.h"
#include "nsSubstring.h"
#include "mozilla/Attributes.h"
#include "mozilla/LazyIdleThread.h"
// The nsHttpNegotiateAuth class provides responses for the GSS-API Negotiate method
// as specified by Microsoft in draft-brezak-spnego-http-04.txt
@ -17,7 +18,7 @@
class nsHttpNegotiateAuth final : public nsIHttpAuthenticator
{
public:
NS_DECL_ISUPPORTS
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIHTTPAUTHENTICATOR
private:
@ -37,5 +38,7 @@ private:
int32_t port,
const char *baseStart,
const char *baseEnd);
// Thread for GenerateCredentialsAsync
RefPtr<mozilla::LazyIdleThread> mNegotiateThread;
};
#endif /* nsHttpNegotiateAuth_h__ */

Просмотреть файл

@ -25,18 +25,13 @@ namespace image {
class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
{
public:
AutoRecordDecoderTelemetry(Decoder* aDecoder, uint32_t aByteCount)
explicit AutoRecordDecoderTelemetry(Decoder* aDecoder)
: mDecoder(aDecoder)
{
MOZ_ASSERT(mDecoder);
// Begin recording telemetry data.
mStartTime = TimeStamp::Now();
mDecoder->mChunkCount++;
// Keep track of the total number of bytes written.
mDecoder->mBytesDecoded += aByteCount;
}
~AutoRecordDecoderTelemetry()
@ -58,17 +53,14 @@ Decoder::Decoder(RasterImage* aImage)
, mImage(aImage)
, mProgress(NoProgress)
, mFrameCount(0)
, mFailCode(NS_OK)
, mChunkCount(0)
, mDecoderFlags(DefaultDecoderFlags())
, mSurfaceFlags(DefaultSurfaceFlags())
, mBytesDecoded(0)
, mInitialized(false)
, mMetadataDecode(false)
, mInFrame(false)
, mDataDone(false)
, mReachedTerminalState(false)
, mDecodeDone(false)
, mDataError(false)
, mError(false)
, mDecodeAborted(false)
, mShouldReportError(false)
{ }
@ -92,7 +84,7 @@ Decoder::~Decoder()
* Common implementation of the decoder interface.
*/
void
nsresult
Decoder::Init()
{
// No re-initializing
@ -106,61 +98,51 @@ Decoder::Init()
// will be retrievable.
MOZ_ASSERT(ShouldUseSurfaceCache() || IsFirstFrameDecode());
// Implementation-specific initialization
InitInternal();
// Implementation-specific initialization.
nsresult rv = InitInternal();
mInitialized = true;
return rv;
}
nsresult
Decoder::Decode(NotNull<IResumable*> aOnResume)
Decoder::Decode(IResumable* aOnResume /* = nullptr */)
{
MOZ_ASSERT(mInitialized, "Should be initialized here");
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
// We keep decoding chunks until the decode completes or there are no more
// chunks available.
while (!GetDecodeDone() && !HasError()) {
auto newState = mIterator->AdvanceOrScheduleResume(aOnResume.get());
if (newState == SourceBufferIterator::WAITING) {
// We can't continue because the rest of the data hasn't arrived from the
// network yet. We don't have to do anything special; the
// SourceBufferIterator will ensure that Decode() gets called again on a
// DecodePool thread when more data is available.
return NS_OK;
}
if (newState == SourceBufferIterator::COMPLETE) {
mDataDone = true;
nsresult finalStatus = mIterator->CompletionStatus();
if (NS_FAILED(finalStatus)) {
PostDataError();
}
CompleteDecode();
return finalStatus;
}
MOZ_ASSERT(newState == SourceBufferIterator::READY);
{
PROFILER_LABEL("ImageDecoder", "Write",
js::ProfileEntry::Category::GRAPHICS);
AutoRecordDecoderTelemetry telemetry(this, mIterator->Length());
// Pass the data along to the implementation.
Maybe<TerminalState> terminalState = DoDecode(*mIterator);
if (terminalState == Some(TerminalState::FAILURE)) {
PostDataError();
}
}
// If we're already done, don't attempt to keep decoding.
if (GetDecodeDone()) {
return HasError() ? NS_ERROR_FAILURE : NS_OK;
}
Maybe<TerminalState> terminalState;
{
PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS);
AutoRecordDecoderTelemetry telemetry(this);
terminalState = DoDecode(*mIterator, aOnResume);
}
if (!terminalState) {
// We need more data to continue. If @aOnResume was non-null, the
// SourceBufferIterator will automatically reschedule us. Otherwise, it's up
// to the caller.
return NS_OK;
}
// We reached a terminal state; we're now done decoding.
mReachedTerminalState = true;
// If decoding failed, record that fact.
if (terminalState == Some(TerminalState::FAILURE)) {
PostError();
}
// Perform final cleanup.
CompleteDecode();
return HasError() ? NS_ERROR_FAILURE : NS_OK;
}
@ -176,12 +158,21 @@ Decoder::ShouldSyncDecode(size_t aByteLimit)
void
Decoder::CompleteDecode()
{
// Implementation-specific finalization
BeforeFinishInternal();
if (!HasError()) {
FinishInternal();
} else {
FinishWithErrorInternal();
// Implementation-specific finalization.
nsresult rv = BeforeFinishInternal();
if (NS_FAILED(rv)) {
PostError();
}
rv = HasError() ? FinishWithErrorInternal()
: FinishInternal();
if (NS_FAILED(rv)) {
PostError();
}
// If this was a metadata decode and we never got a size, the decode failed.
if (IsMetadataDecode() && !HasSize()) {
PostError();
}
// If the implementation left us mid-frame, finish that up.
@ -196,9 +187,9 @@ Decoder::CompleteDecode()
if (!IsMetadataDecode() && !mDecodeDone && !WasAborted()) {
mShouldReportError = true;
// If we only have a data error, we're usable if we have at least one
// complete frame.
if (!HasDecoderError() && GetCompleteFrameCount() > 0) {
// Even if we encountered an error, we're still usable if we have at least
// one complete frame.
if (GetCompleteFrameCount() > 0) {
// We're usable, so do exactly what we should have when the decoder
// completed.
@ -276,8 +267,6 @@ Decoder::AllocateFrame(uint32_t aFrameNum,
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
mInFrame = true;
}
} else {
PostDataError();
}
return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
@ -291,7 +280,7 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
uint8_t aPaletteDepth,
imgFrame* aPreviousFrame)
{
if (mDataError || NS_FAILED(mFailCode)) {
if (HasError()) {
return RawAccessFrameRef();
}
@ -388,10 +377,10 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
* Hook stubs. Override these as necessary in decoder implementations.
*/
void Decoder::InitInternal() { }
void Decoder::BeforeFinishInternal() { }
void Decoder::FinishInternal() { }
void Decoder::FinishWithErrorInternal() { }
nsresult Decoder::InitInternal() { return NS_OK; }
nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
nsresult Decoder::FinishInternal() { return NS_OK; }
nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }
/*
* Progress Notifications
@ -494,25 +483,9 @@ Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
}
void
Decoder::PostDataError()
Decoder::PostError()
{
mDataError = true;
if (mInFrame && mCurrentFrame) {
mCurrentFrame->Abort();
}
}
void
Decoder::PostDecoderError(nsresult aFailureCode)
{
MOZ_ASSERT(NS_FAILED(aFailureCode), "Not a failure code!");
mFailCode = aFailureCode;
// XXXbholley - we should report the image URI here, but imgContainer
// needs to know its URI first
NS_WARNING("Image decoding error - This is probably a bug!");
mError = true;
if (mInFrame && mCurrentFrame) {
mCurrentFrame->Abort();

Просмотреть файл

@ -37,18 +37,20 @@ public:
/**
* Initialize an image decoder. Decoders may not be re-initialized.
*
* @return NS_OK if the decoder could be initialized successfully.
*/
void Init();
nsresult Init();
/**
* Decodes, reading all data currently available in the SourceBuffer.
*
* If more data is needed, Decode() will schedule @aOnResume to be called when
* more data is available.
* If more data is needed and @aOnResume is non-null, Decode() will schedule
* @aOnResume to be called when more data is available.
*
* Any errors are reported by setting the appropriate state on the decoder.
*/
nsresult Decode(NotNull<IResumable*> aOnResume);
nsresult Decode(IResumable* aOnResume = nullptr);
/**
* Given a maximum number of bytes we're willing to decode, @aByteLimit,
@ -162,13 +164,21 @@ public:
return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
}
size_t BytesDecoded() const { return mBytesDecoded; }
size_t BytesDecoded() const
{
MOZ_ASSERT(mIterator);
return mIterator->ByteCount();
}
// The amount of time we've spent inside DoDecode() so far for this decoder.
TimeDuration DecodeTime() const { return mDecodeTime; }
// The number of chunks this decoder's input was divided into.
uint32_t ChunkCount() const { return mChunkCount; }
uint32_t ChunkCount() const
{
MOZ_ASSERT(mIterator);
return mIterator->ChunkCount();
}
// The number of frames we have, including anything in-progress. Thus, this
// is only 0 if we haven't begun any frames.
@ -184,17 +194,14 @@ public:
bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
// Error tracking
bool HasError() const { return HasDataError() || HasDecoderError(); }
bool HasDataError() const { return mDataError; }
bool HasDecoderError() const { return NS_FAILED(mFailCode); }
bool HasError() const { return mError; }
bool ShouldReportError() const { return mShouldReportError; }
nsresult GetDecoderError() const { return mFailCode; }
/// Did we finish decoding enough that calling Decode() again would be useless?
bool GetDecodeDone() const
{
return mDecodeDone || (mMetadataDecode && HasSize()) ||
HasError() || mDataDone;
return mReachedTerminalState || mDecodeDone ||
(mMetadataDecode && HasSize()) || HasError();
}
/// Are we in the middle of a frame right now? Used for assertions only.
@ -287,13 +294,14 @@ protected:
*
* BeforeFinishInternal() can be used to detect if decoding is in an
* incomplete state, e.g. due to file truncation, in which case it should
* call PostDataError().
* return a failing nsresult.
*/
virtual void InitInternal();
virtual Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) = 0;
virtual void BeforeFinishInternal();
virtual void FinishInternal();
virtual void FinishWithErrorInternal();
virtual nsresult InitInternal();
virtual Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) = 0;
virtual nsresult BeforeFinishInternal();
virtual nsresult FinishInternal();
virtual nsresult FinishWithErrorInternal();
/*
* Progress notifications.
@ -358,17 +366,6 @@ protected:
// means a single iteration, stopping on the last frame.
void PostDecodeDone(int32_t aLoopCount = 0);
// Data errors are the fault of the source data, decoder errors are our fault
void PostDataError();
void PostDecoderError(nsresult aFailCode);
/**
* CompleteDecode() finishes up the decoding process after Decode() determines
* that we're finished. It records final progress and does all the cleanup
* that's possible off-main-thread.
*/
void CompleteDecode();
/**
* Allocates a new frame, making it our current frame if successful.
*
@ -390,6 +387,17 @@ protected:
gfx::SurfaceFormat::B8G8R8A8);
}
private:
/// Report that an error was encountered while decoding.
void PostError();
/**
* CompleteDecode() finishes up the decoding process after Decode() determines
* that we're finished. It records final progress and does all the cleanup
* that's possible off-main-thread.
*/
void CompleteDecode();
RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
const nsIntSize& aTargetSize,
const nsIntRect& aFrameRect,
@ -415,22 +423,18 @@ private:
uint32_t mFrameCount; // Number of frames, including anything in-progress
nsresult mFailCode;
// Telemetry data for this decoder.
TimeDuration mDecodeTime;
uint32_t mChunkCount;
DecoderFlags mDecoderFlags;
SurfaceFlags mSurfaceFlags;
size_t mBytesDecoded;
bool mInitialized : 1;
bool mMetadataDecode : 1;
bool mInFrame : 1;
bool mDataDone : 1;
bool mReachedTerminalState : 1;
bool mDecodeDone : 1;
bool mDataError : 1;
bool mError : 1;
bool mDecodeAborted : 1;
bool mShouldReportError : 1;
};

Просмотреть файл

@ -135,8 +135,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
}
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
@ -180,8 +179,7 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
decoder->SetSurfaceFlags(aSurfaceFlags);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
@ -218,8 +216,7 @@ DecoderFactory::CreateMetadataDecoder(DecoderType aType,
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetSampleSize(aSampleSize);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
@ -267,8 +264,7 @@ DecoderFactory::CreateDecoderForICOResource(DecoderType aType,
MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
}
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
@ -313,8 +309,7 @@ DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
}
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
@ -338,8 +333,7 @@ DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY);
decoder->Init();
if (NS_FAILED(decoder->GetDecoderError())) {
if (NS_FAILED(decoder->Init())) {
return nullptr;
}

Просмотреть файл

@ -1782,7 +1782,7 @@ RasterImage::ReportDecoderError(Decoder* aDecoder)
nsCOMPtr<nsIScriptError> errorObject =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
if (consoleService && errorObject && !aDecoder->HasDecoderError()) {
if (consoleService && errorObject) {
nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated."));
nsAutoString src;
if (GetURI()) {

Просмотреть файл

@ -30,11 +30,67 @@ SourceBufferIterator::~SourceBufferIterator()
}
}
SourceBufferIterator&
SourceBufferIterator::operator=(SourceBufferIterator&& aOther)
{
if (mOwner) {
mOwner->OnIteratorRelease();
}
mOwner = Move(aOther.mOwner);
mState = aOther.mState;
mData = aOther.mData;
mChunkCount = aOther.mChunkCount;
mByteCount = aOther.mByteCount;
return *this;
}
SourceBufferIterator::State
SourceBufferIterator::AdvanceOrScheduleResume(IResumable* aConsumer)
SourceBufferIterator::AdvanceOrScheduleResume(size_t aRequestedBytes,
IResumable* aConsumer)
{
MOZ_ASSERT(mOwner);
return mOwner->AdvanceIteratorOrScheduleResume(*this, aConsumer);
if (MOZ_UNLIKELY(!HasMore())) {
MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
return COMPLETE;
}
// The range of data [mOffset, mOffset + mNextReadLength) has just been read
// by the caller (or at least they don't have any interest in it), so consume
// that data.
MOZ_ASSERT(mData.mIterating.mNextReadLength <= mData.mIterating.mAvailableLength);
mData.mIterating.mOffset += mData.mIterating.mNextReadLength;
mData.mIterating.mAvailableLength -= mData.mIterating.mNextReadLength;
mData.mIterating.mNextReadLength = 0;
if (MOZ_LIKELY(mState == READY)) {
// If the caller wants zero bytes of data, that's easy enough; we just
// configured ourselves for a zero-byte read above! In theory we could do
// this even in the START state, but it's not important for performance and
// breaking the ability of callers to assert that the pointer returned by
// Data() is non-null doesn't seem worth it.
if (aRequestedBytes == 0) {
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0);
return READY;
}
// Try to satisfy the request out of our local buffer. This is potentially
// much faster than requesting data from our owning SourceBuffer because we
// don't have to take the lock. Note that if we have anything at all in our
// local buffer, we use it to satisfy the request; @aRequestedBytes is just
// the *maximum* number of bytes we can return.
if (mData.mIterating.mAvailableLength > 0) {
return AdvanceFromLocalBuffer(aRequestedBytes);
}
}
// Our local buffer is empty, so we'll have to request data from our owning
// SourceBuffer.
return mOwner->AdvanceIteratorOrScheduleResume(*this,
aRequestedBytes,
aConsumer);
}
bool
@ -49,6 +105,8 @@ SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const
// SourceBuffer implementation.
//////////////////////////////////////////////////////////////////////////////
const size_t SourceBuffer::MIN_CHUNK_CAPACITY;
SourceBuffer::SourceBuffer()
: mMutex("image::SourceBuffer")
, mConsumerCount(0)
@ -221,7 +279,9 @@ SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
mWaitingConsumers.AppendElement(aConsumer);
if (aConsumer) {
mWaitingConsumers.AppendElement(aConsumer);
}
}
void
@ -504,7 +564,7 @@ SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk;
size_t iteratorOffset = aIterator.mData.mIterating.mOffset;
size_t iteratorLength = aIterator.mData.mIterating.mLength;
size_t iteratorLength = aIterator.mData.mIterating.mAvailableLength;
// Include the bytes the iterator is currently pointing to in the limit, so
// that the current chunk doesn't have to be a special case.
@ -526,14 +586,13 @@ SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
SourceBufferIterator::State
SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
size_t aRequestedBytes,
IResumable* aConsumer)
{
MutexAutoLock lock(mMutex);
if (MOZ_UNLIKELY(!aIterator.HasMore())) {
MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
return SourceBufferIterator::COMPLETE;
}
MOZ_ASSERT(aIterator.HasMore(), "Advancing a completed iterator and "
"AdvanceOrScheduleResume didn't catch it");
if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) {
// This SourceBuffer is complete due to an error; all reads fail.
@ -551,14 +610,15 @@ SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
const Chunk& currentChunk = mChunks[iteratorChunkIdx];
size_t iteratorEnd = aIterator.mData.mIterating.mOffset +
aIterator.mData.mIterating.mLength;
aIterator.mData.mIterating.mAvailableLength;
MOZ_ASSERT(iteratorEnd <= currentChunk.Length());
MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity());
if (iteratorEnd < currentChunk.Length()) {
// There's more data in the current chunk.
return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(),
iteratorEnd, currentChunk.Length() - iteratorEnd);
iteratorEnd, currentChunk.Length() - iteratorEnd,
aRequestedBytes);
}
if (iteratorEnd == currentChunk.Capacity() &&
@ -566,7 +626,7 @@ SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
// Advance to the next chunk.
const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1];
return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0,
nextChunk.Length());
nextChunk.Length(), aRequestedBytes);
}
MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced");

Просмотреть файл

@ -80,29 +80,28 @@ public:
explicit SourceBufferIterator(SourceBuffer* aOwner)
: mOwner(aOwner)
, mState(START)
, mChunkCount(0)
, mByteCount(0)
{
MOZ_ASSERT(aOwner);
mData.mIterating.mChunk = 0;
mData.mIterating.mData = nullptr;
mData.mIterating.mOffset = 0;
mData.mIterating.mLength = 0;
mData.mIterating.mAvailableLength = 0;
mData.mIterating.mNextReadLength = 0;
}
SourceBufferIterator(SourceBufferIterator&& aOther)
: mOwner(Move(aOther.mOwner))
, mState(aOther.mState)
, mData(aOther.mData)
, mChunkCount(aOther.mChunkCount)
, mByteCount(aOther.mByteCount)
{ }
~SourceBufferIterator();
SourceBufferIterator& operator=(SourceBufferIterator&& aOther)
{
mOwner = Move(aOther.mOwner);
mState = aOther.mState;
mData = aOther.mData;
return *this;
}
SourceBufferIterator& operator=(SourceBufferIterator&& aOther);
/**
* Returns true if there are no more than @aBytes remaining in the
@ -111,11 +110,45 @@ public:
bool RemainingBytesIsNoMoreThan(size_t aBytes) const;
/**
* Advances the iterator through the SourceBuffer if possible. If not,
* Advances the iterator through the SourceBuffer if possible. Advances no
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
* possible.)
*
* This is a wrapper around AdvanceOrScheduleResume() that makes it clearer at
* the callsite when the no resuming is intended.
*
* @return State::READY if the iterator was successfully advanced.
* State::WAITING if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer, but the SourceBuffer
* may still receive additional data.
* State::COMPLETE if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer and the SourceBuffer is
* marked complete (i.e., it will never receive any additional
* data).
*/
State Advance(size_t aRequestedBytes)
{
return AdvanceOrScheduleResume(aRequestedBytes, nullptr);
}
/**
* Advances the iterator through the SourceBuffer if possible. Advances no
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
* possible.) If advancing is not possible and @aConsumer is not null,
* arranges to call the @aConsumer's Resume() method when more data is
* available.
*
* @return State::READY if the iterator was successfully advanced.
* State::WAITING if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer, but the SourceBuffer
* may still receive additional data. @aConsumer's Resume() method
* will be called when additional data is available.
* State::COMPLETE if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer and the SourceBuffer is
* marked complete (i.e., it will never receive any additional
* data).
*/
State AdvanceOrScheduleResume(IResumable* aConsumer);
State AdvanceOrScheduleResume(size_t aRequestedBytes, IResumable* aConsumer);
/// If at the end, returns the status passed to SourceBuffer::Complete().
nsresult CompletionStatus() const
@ -137,9 +170,15 @@ public:
size_t Length() const
{
MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state");
return mState == READY ? mData.mIterating.mLength : 0;
return mState == READY ? mData.mIterating.mNextReadLength : 0;
}
/// @return a count of the chunks we've advanced through.
uint32_t ChunkCount() const { return mChunkCount; }
/// @return a count of the bytes in all chunks we've advanced through.
size_t ByteCount() const { return mByteCount; }
private:
friend class SourceBuffer;
@ -148,15 +187,39 @@ private:
bool HasMore() const { return mState != COMPLETE; }
State AdvanceFromLocalBuffer(size_t aRequestedBytes)
{
MOZ_ASSERT(mState == READY, "Advancing in the wrong state");
MOZ_ASSERT(mData.mIterating.mAvailableLength > 0,
"The local buffer shouldn't be empty");
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0,
"Advancing without consuming previous data");
mData.mIterating.mNextReadLength =
std::min(mData.mIterating.mAvailableLength, aRequestedBytes);
return READY;
}
State SetReady(uint32_t aChunk, const char* aData,
size_t aOffset, size_t aLength)
size_t aOffset, size_t aAvailableLength,
size_t aRequestedBytes)
{
MOZ_ASSERT(mState != COMPLETE);
mState = READY;
// Update state.
mData.mIterating.mChunk = aChunk;
mData.mIterating.mData = aData;
mData.mIterating.mOffset = aOffset;
mData.mIterating.mLength = aLength;
return mState = READY;
mData.mIterating.mAvailableLength = aAvailableLength;
// Update metrics.
mChunkCount++;
mByteCount += aAvailableLength;
// Attempt to advance by the requested number of bytes.
return AdvanceFromLocalBuffer(aRequestedBytes);
}
State SetWaiting()
@ -186,12 +249,16 @@ private:
uint32_t mChunk;
const char* mData;
size_t mOffset;
size_t mLength;
size_t mAvailableLength;
size_t mNextReadLength;
} mIterating;
struct {
nsresult mStatus;
} mAtEnd;
} mData;
uint32_t mChunkCount; // Count of chunks we've advanced through.
size_t mByteCount; // Count of bytes in all chunks we've advanced through.
};
/**
@ -256,6 +323,18 @@ public:
SourceBufferIterator Iterator();
//////////////////////////////////////////////////////////////////////////////
// Consumer methods.
//////////////////////////////////////////////////////////////////////////////
/**
* The minimum chunk capacity we'll allocate, if we don't know the correct
* capacity (which would happen because ExpectLength() wasn't called or gave
* us the wrong value). This is only exposed for use by tests; if normal code
* is using this, it's doing something wrong.
*/
static const size_t MIN_CHUNK_CAPACITY = 4096;
private:
friend class SourceBufferIterator;
@ -337,6 +416,7 @@ private:
typedef SourceBufferIterator::State State;
State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
size_t aRequestedBytes,
IResumable* aConsumer);
bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
size_t aBytes) const;
@ -356,8 +436,6 @@ private:
// Member variables.
//////////////////////////////////////////////////////////////////////////////
static const size_t MIN_CHUNK_CAPACITY = 4096;
/// All private members are protected by mMutex.
mutable Mutex mMutex;

Просмотреть файл

@ -14,6 +14,7 @@
#define mozilla_image_StreamingLexer_h
#include <algorithm>
#include <cstdint>
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
@ -30,13 +31,16 @@ enum class BufferingStrategy
UNBUFFERED // Data will be processed as it arrives, in multiple chunks.
};
/// The result of a call to StreamingLexer::Lex().
/// Possible terminal states for the lexer.
enum class TerminalState
{
SUCCESS,
FAILURE
};
/// The result of a call to StreamingLexer::Lex().
typedef Variant<TerminalState> LexerResult;
/**
* LexerTransition is a type used to give commands to the lexing framework.
* Code that uses StreamingLexer can create LexerTransition values using the
@ -258,12 +262,16 @@ class StreamingLexer
{
public:
explicit StreamingLexer(LexerTransition<State> aStartState)
: mTransition(aStartState)
: mTransition(TerminalState::FAILURE)
, mToReadUnbuffered(0)
{ }
{
SetTransition(aStartState);
}
template <typename Func>
Maybe<TerminalState> Lex(const char* aInput, size_t aLength, Func aFunc)
Maybe<TerminalState> Lex(SourceBufferIterator& aIterator,
IResumable* aOnResume,
Func aFunc)
{
if (mTransition.NextStateIsTerminal()) {
// We've already reached a terminal state. We never deliver any more data
@ -271,138 +279,145 @@ public:
return Some(mTransition.NextStateAsTerminal());
}
Maybe<LexerResult> result;
do {
// Figure out how much we need to read.
const size_t toRead = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
? mToReadUnbuffered
: mTransition.Size() - mBuffer.length();
// Attempt to advance the iterator by |toRead| bytes.
switch (aIterator.AdvanceOrScheduleResume(toRead, aOnResume)) {
case SourceBufferIterator::WAITING:
// We can't continue because the rest of the data hasn't arrived from
// the network yet. We don't have to do anything special; the
// SourceBufferIterator will ensure that |aOnResume| gets called when
// more data is available.
return Nothing();
case SourceBufferIterator::COMPLETE:
// Normally even if the data is truncated, we want decoding to
// succeed so we can display whatever we got. However, if the
// SourceBuffer was completed with a failing status, we want to fail.
// This happens only in exceptional situations like SourceBuffer
// itself encountering a failure due to OOM.
result = SetTransition(NS_SUCCEEDED(aIterator.CompletionStatus())
? Transition::TerminateSuccess()
: Transition::TerminateFailure());
break;
case SourceBufferIterator::READY:
// Process the new data that became available.
MOZ_ASSERT(aIterator.Data());
result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
? UnbufferedRead(aIterator, aFunc)
: BufferedRead(aIterator, aFunc);
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown SourceBufferIterator state");
result = SetTransition(Transition::TerminateFailure());
}
} while (!result);
// Map |LexerResult| onto the old |Maybe<TerminalState>| API.
return result->is<TerminalState>() ? Some(result->as<TerminalState>())
: Nothing();
}
private:
template <typename Func>
Maybe<LexerResult> UnbufferedRead(SourceBufferIterator& aIterator, Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
MOZ_ASSERT(mBuffer.empty(),
"Buffered read at the same time as unbuffered read?");
if (mToReadUnbuffered > 0) {
// We're continuing an unbuffered read.
MOZ_ASSERT(mBuffer.empty(),
"Shouldn't be continuing an unbuffered read and a buffered "
"read at the same time");
size_t toRead = std::min(mToReadUnbuffered, aLength);
// Call aFunc with the unbuffered state to indicate that we're in the
// middle of an unbuffered read. We enforce that any state transition
// passed back to us is either a terminal state or takes us back to the
// unbuffered state.
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, toRead);
aFunc(mTransition.UnbufferedState(), aIterator.Data(), aIterator.Length());
if (unbufferedTransition.NextStateIsTerminal()) {
mTransition = unbufferedTransition;
return Some(mTransition.NextStateAsTerminal()); // Done!
return SetTransition(unbufferedTransition);
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
aInput += toRead;
aLength -= toRead;
mToReadUnbuffered -= toRead;
mToReadUnbuffered -= aIterator.Length();
if (mToReadUnbuffered != 0) {
return Nothing(); // Need more input.
}
// We're done with the unbuffered read, so transition to the next state.
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
if (mTransition.NextStateIsTerminal()) {
return Some(mTransition.NextStateAsTerminal()); // Done!
}
} else if (0 < mBuffer.length()) {
// We're continuing a buffered read.
MOZ_ASSERT(mToReadUnbuffered == 0,
"Shouldn't be continuing an unbuffered read and a buffered "
"read at the same time");
MOZ_ASSERT(mBuffer.length() < mTransition.Size(),
"Buffered more than we needed?");
size_t toRead = std::min(aLength, mTransition.Size() - mBuffer.length());
if (!mBuffer.append(aInput, toRead)) {
return Some(TerminalState::FAILURE);
}
aInput += toRead;
aLength -= toRead;
if (mBuffer.length() != mTransition.Size()) {
return Nothing(); // Need more input.
}
// We've buffered everything, so transition to the next state.
mTransition =
aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length());
mBuffer.clear();
if (mTransition.NextStateIsTerminal()) {
return Some(mTransition.NextStateAsTerminal()); // Done!
return Nothing(); // Keep processing.
}
}
MOZ_ASSERT(mToReadUnbuffered == 0);
MOZ_ASSERT(mBuffer.empty());
// Process states as long as we continue to have enough input to do so.
while (mTransition.Size() <= aLength) {
size_t toRead = mTransition.Size();
if (mTransition.Buffering() == BufferingStrategy::BUFFERED) {
mTransition = aFunc(mTransition.NextState(), aInput, toRead);
} else {
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
// Call aFunc with the unbuffered state to indicate that we're in the
// middle of an unbuffered read. We enforce that any state transition
// passed back to us is either a terminal state or takes us back to the
// unbuffered state.
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, toRead);
if (unbufferedTransition.NextStateIsTerminal()) {
mTransition = unbufferedTransition;
return Some(mTransition.NextStateAsTerminal()); // Done!
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
// We're done with the unbuffered read, so transition to the next state.
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
}
aInput += toRead;
aLength -= toRead;
if (mTransition.NextStateIsTerminal()) {
return Some(mTransition.NextStateAsTerminal()); // Done!
}
}
if (aLength == 0) {
// We finished right at a transition point. Just wait for more data.
return Nothing();
}
// If the next state is unbuffered, deliver what we can and then wait.
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aInput, aLength);
if (unbufferedTransition.NextStateIsTerminal()) {
mTransition = unbufferedTransition;
return Some(mTransition.NextStateAsTerminal()); // Done!
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
mToReadUnbuffered = mTransition.Size() - aLength;
return Nothing(); // Need more input.
}
// If the next state is buffered, buffer what we can and then wait.
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
if (!mBuffer.reserve(mTransition.Size())) {
return Some(TerminalState::FAILURE); // Done due to allocation failure.
}
if (!mBuffer.append(aInput, aLength)) {
return Some(TerminalState::FAILURE);
}
return Nothing(); // Need more input.
// We're done with the unbuffered read, so transition to the next state.
return SetTransition(aFunc(mTransition.NextState(), nullptr, 0));
}
template <typename Func>
Maybe<LexerResult> BufferedRead(SourceBufferIterator& aIterator, Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
MOZ_ASSERT(mToReadUnbuffered == 0,
"Buffered read at the same time as unbuffered read?");
MOZ_ASSERT(mBuffer.length() < mTransition.Size() ||
(mBuffer.length() == 0 && mTransition.Size() == 0),
"Buffered more than we needed?");
// If we have all the data, we don't actually need to buffer anything.
if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) {
return SetTransition(aFunc(mTransition.NextState(),
aIterator.Data(),
aIterator.Length()));
}
// We do need to buffer, so make sure the buffer has enough capacity. We
// deliberately wait until we know for sure we need to buffer to call
// reserve() since it could require memory allocation.
if (!mBuffer.reserve(mTransition.Size())) {
return SetTransition(Transition::TerminateFailure());
}
// Append the new data we just got to the buffer.
if (!mBuffer.append(aIterator.Data(), aIterator.Length())) {
return SetTransition(Transition::TerminateFailure());
}
if (mBuffer.length() != mTransition.Size()) {
return Nothing(); // Keep processing.
}
// We've buffered everything, so transition to the next state.
return SetTransition(aFunc(mTransition.NextState(),
mBuffer.begin(),
mBuffer.length()));
}
Maybe<LexerResult> SetTransition(const LexerTransition<State>& aTransition)
{
mTransition = aTransition;
// Get rid of anything left over from the previous state.
mBuffer.clear();
mToReadUnbuffered = 0;
// If we reached a terminal state, let the caller know.
if (mTransition.NextStateIsTerminal()) {
return Some(LexerResult(mTransition.NextStateAsTerminal()));
}
// If we're entering an unbuffered state, record how long we'll stay in it.
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
mToReadUnbuffered = mTransition.Size();
}
return Nothing(); // Keep processing.
}
private:
Vector<char, InlineBufferSize> mBuffer;
LexerTransition<State> mTransition;
size_t mToReadUnbuffered;

Просмотреть файл

@ -660,6 +660,11 @@ public:
return aCost <= mMaxCost;
}
size_t MaximumCapacity() const
{
return size_t(mMaxCost);
}
void LockImage(const ImageKey aImageKey)
{
RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
@ -1126,5 +1131,16 @@ SurfaceCache::CollectSizeOfSurfaces(const ImageKey aImageKey,
return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf);
}
/* static */ size_t
SurfaceCache::MaximumCapacity()
{
if (!sInstance) {
return 0;
}
MutexAutoLock lock(sInstance->GetMutex());
return sInstance->MaximumCapacity();
}
} // namespace image
} // namespace mozilla

Просмотреть файл

@ -399,6 +399,12 @@ struct SurfaceCache
nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf);
/**
* @return maximum capacity of the SurfaceCache in bytes. This is only exposed
* for use by tests; normal code should use CanHold() instead.
*/
static size_t MaximumCapacity();
private:
virtual ~SurfaceCache() = 0; // Forbid instantiation.
};

Просмотреть файл

@ -222,15 +222,17 @@ nsBMPDecoder::GetCompressedImageSize() const
: mH.mImageSize;
}
void
nsresult
nsBMPDecoder::BeforeFinishInternal()
{
if (!IsMetadataDecode() && !mImageData) {
PostDataError();
return NS_ERROR_FAILURE; // No image; something went wrong.
}
return NS_OK;
}
void
nsresult
nsBMPDecoder::FinishInternal()
{
// We shouldn't be called in error cases.
@ -260,14 +262,23 @@ nsBMPDecoder::FinishInternal()
nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight());
PostInvalidation(r);
if (mDoesHaveTransparency) {
MOZ_ASSERT(mMayHaveTransparency);
PostFrameStop(Opacity::SOME_TRANSPARENCY);
} else {
PostFrameStop(Opacity::FULLY_OPAQUE);
}
MOZ_ASSERT_IF(mDoesHaveTransparency, mMayHaveTransparency);
// We have transparency if we either detected some in the image itself
// (i.e., |mDoesHaveTransparency| is true) or we're in an ICO, which could
// mean we have an AND mask that provides transparency (i.e., |mIsWithinICO|
// is true).
// XXX(seth): We can tell when we create the decoder if the AND mask is
// present, so we could be more precise about this.
const Opacity opacity = mDoesHaveTransparency || mIsWithinICO
? Opacity::SOME_TRANSPARENCY
: Opacity::FULLY_OPAQUE;
PostFrameStop(opacity);
PostDecodeDone();
}
return NS_OK;
}
// ----------------------------------------
@ -436,13 +447,11 @@ nsBMPDecoder::FinishRow()
}
Maybe<TerminalState>
nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator)
nsBMPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
MOZ_ASSERT(aIterator.Data());
MOZ_ASSERT(aIterator.Length() > 0);
return mLexer.Lex(aIterator.Data(), aIterator.Length(),
return mLexer.Lex(aIterator, aOnResume,
[=](State aState, const char* aData, size_t aLength) {
switch (aState) {
case State::FILE_HEADER: return ReadFileHeader(aData, aLength);
@ -469,7 +478,6 @@ nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength)
bool signatureOk = aData[0] == 'B' && aData[1] == 'M';
if (!signatureOk) {
PostDataError();
return Transition::TerminateFailure();
}
@ -496,7 +504,6 @@ nsBMPDecoder::ReadInfoHeaderSize(const char* aData, size_t aLength)
(mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN &&
mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX);
if (!bihSizeOk) {
PostDataError();
return Transition::TerminateFailure();
}
// ICO BMPs must have a WinBMPv3 header. nsICODecoder should have already
@ -551,7 +558,6 @@ nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth &&
mH.mHeight != INT_MIN;
if (!sizeOk) {
PostDataError();
return Transition::TerminateFailure();
}
@ -570,14 +576,11 @@ nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
mH.mBIHSize == InfoHeaderLength::WIN_V5) &&
(mH.mBpp == 16 || mH.mBpp == 32));
if (!bppCompressionOk) {
PostDataError();
return Transition::TerminateFailure();
}
// Post our size to the superclass.
uint32_t absHeight = AbsoluteHeight();
PostSize(mH.mWidth, absHeight);
mCurrentRow = absHeight;
// Initialize our current row to the top of the image.
mCurrentRow = AbsoluteHeight();
// Round it up to the nearest byte count, then pad to 4-byte boundary.
// Compute this even for a metadate decode because GetCompressedImageSize()
@ -645,6 +648,9 @@ nsBMPDecoder::ReadBitfields(const char* aData, size_t aLength)
PostHasTransparency();
}
// Post our size to the superclass.
PostSize(mH.mWidth, AbsoluteHeight());
// We've now read all the headers. If we're doing a metadata decode, we're
// done.
if (IsMetadataDecode()) {
@ -715,7 +721,6 @@ nsBMPDecoder::ReadColorTable(const char* aData, size_t aLength)
// points into the middle of the color palette instead of past the end) and
// we give up.
if (mPreGapLength > mH.mDataOffset) {
PostDataError();
return Transition::TerminateFailure();
}

Просмотреть файл

@ -142,16 +142,10 @@ public:
/// bitmap has been fully decoded.)
bool HasTransparency() const { return mDoesHaveTransparency; }
/// Force transparency from outside. (Used by the ICO decoder.)
void SetHasTransparency()
{
mMayHaveTransparency = true;
mDoesHaveTransparency = true;
}
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) override;
virtual void BeforeFinishInternal() override;
virtual void FinishInternal() override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
nsresult BeforeFinishInternal() override;
nsresult FinishInternal() override;
private:
friend class DecoderFactory;

Просмотреть файл

@ -100,7 +100,7 @@ nsGIFDecoder2::~nsGIFDecoder2()
free(mGIFStruct.local_colormap);
}
void
nsresult
nsGIFDecoder2::FinishInternal()
{
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
@ -113,6 +113,8 @@ nsGIFDecoder2::FinishInternal()
PostDecodeDone(mGIFStruct.loop_count - 1);
mGIFOpen = false;
}
return NS_OK;
}
void
@ -455,13 +457,11 @@ ConvertColormap(uint32_t* aColormap, uint32_t aColors)
}
Maybe<TerminalState>
nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator)
nsGIFDecoder2::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
MOZ_ASSERT(aIterator.Data());
MOZ_ASSERT(aIterator.Length() > 0);
return mLexer.Lex(aIterator.Data(), aIterator.Length(),
return mLexer.Lex(aIterator, aOnResume,
[=](State aState, const char* aData, size_t aLength) {
switch(aState) {
case State::GIF_HEADER:

Просмотреть файл

@ -24,8 +24,9 @@ class nsGIFDecoder2 : public Decoder
public:
~nsGIFDecoder2();
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) override;
virtual void FinishInternal() override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
nsresult FinishInternal() override;
virtual Telemetry::ID SpeedHistogram() override;
private:

Просмотреть файл

@ -54,7 +54,6 @@ nsICODecoder::GetNumColors()
nsICODecoder::nsICODecoder(RasterImage* aImage)
: Decoder(aImage)
, mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
, mDoNotResume(WrapNotNull(new DoNotResume))
, mBiggestResourceColorDepth(0)
, mBestResourceDelta(INT_MIN)
, mBestResourceColorDepth(0)
@ -67,26 +66,26 @@ nsICODecoder::nsICODecoder(RasterImage* aImage)
, mHasMaskAlpha(false)
{ }
void
nsresult
nsICODecoder::FinishInternal()
{
// We shouldn't be called in error cases
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
GetFinalStateFromContainedDecoder();
return GetFinalStateFromContainedDecoder();
}
void
nsresult
nsICODecoder::FinishWithErrorInternal()
{
GetFinalStateFromContainedDecoder();
return GetFinalStateFromContainedDecoder();
}
void
nsresult
nsICODecoder::GetFinalStateFromContainedDecoder()
{
if (!mContainedDecoder) {
return;
return NS_OK;
}
MOZ_ASSERT(mContainedSourceBuffer,
@ -95,22 +94,23 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
// Let the contained decoder finish up if necessary.
if (!mContainedSourceBuffer->IsComplete()) {
mContainedSourceBuffer->Complete(NS_OK);
if (NS_FAILED(mContainedDecoder->Decode(mDoNotResume))) {
PostDataError();
}
mContainedDecoder->Decode();
}
// Make our state the same as the state of the contained decoder.
mDecodeDone = mContainedDecoder->GetDecodeDone();
mDataError = mDataError || mContainedDecoder->HasDataError();
mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError()
: mFailCode;
mDecodeAborted = mContainedDecoder->WasAborted();
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsFinished());
// Propagate errors.
nsresult rv = HasError() || mContainedDecoder->HasError()
? NS_ERROR_FAILURE
: NS_OK;
MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished());
return rv;
}
bool
@ -572,15 +572,6 @@ nsICODecoder::FinishMask()
}
}
// If the mask contained any transparent pixels, record that fact.
if (mHasMaskAlpha) {
PostHasTransparency();
RefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
bmpDecoder->SetHasTransparency();
}
return Transition::To(ICOState::FINISHED_RESOURCE, 0);
}
@ -598,13 +589,11 @@ nsICODecoder::FinishResource()
}
Maybe<TerminalState>
nsICODecoder::DoDecode(SourceBufferIterator& aIterator)
nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
MOZ_ASSERT(aIterator.Data());
MOZ_ASSERT(aIterator.Length() > 0);
return mLexer.Lex(aIterator.Data(), aIterator.Length(),
return mLexer.Lex(aIterator, aOnResume,
[=](ICOState aState, const char* aData, size_t aLength) {
switch (aState) {
case ICOState::HEADER:
@ -649,25 +638,25 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
// reading from.
mContainedSourceBuffer->Append(aBuffer, aCount);
bool succeeded = true;
// Write to the contained decoder. If we run out of data, the ICO decoder will
// get resumed when there's more data available, as usual, so we don't need
// the contained decoder to get resumed too. To avoid that, we provide an
// IResumable which just does nothing.
if (NS_FAILED(mContainedDecoder->Decode(mDoNotResume))) {
PostDataError();
if (NS_FAILED(mContainedDecoder->Decode())) {
succeeded = false;
}
// Make our state the same as the state of the contained decoder.
// Make our state the same as the state of the contained decoder, and
// propagate errors.
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
if (mContainedDecoder->HasDataError()) {
PostDataError();
}
if (mContainedDecoder->HasDecoderError()) {
PostDecoderError(mContainedDecoder->GetDecoderError());
if (mContainedDecoder->HasError()) {
succeeded = false;
}
return !HasError();
return succeeded;
}
} // namespace image

Просмотреть файл

@ -11,7 +11,6 @@
#include "Decoder.h"
#include "imgFrame.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/NotNull.h"
#include "nsBMPDecoder.h"
#include "nsPNGDecoder.h"
#include "ICOFileHeaders.h"
@ -70,9 +69,10 @@ public:
/// @return The offset from the beginning of the ICO to the first resource.
size_t FirstResourceOffset() const;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) override;
virtual void FinishInternal() override;
virtual void FinishWithErrorInternal() override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
nsresult FinishInternal() override;
nsresult FinishWithErrorInternal() override;
private:
friend class DecoderFactory;
@ -85,7 +85,7 @@ private:
bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
// Gets decoder state from the contained decoder so it's visible externally.
void GetFinalStateFromContainedDecoder();
nsresult GetFinalStateFromContainedDecoder();
/**
* Verifies that the width and height values in @aBIH are valid and match the
@ -111,22 +111,9 @@ private:
LexerTransition<ICOState> FinishMask();
LexerTransition<ICOState> FinishResource();
// A helper implementation of IResumable which just does nothing; see
// WriteToContainedDecoder() for more details.
class DoNotResume final : public IResumable
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DoNotResume, override)
void Resume() override { }
private:
virtual ~DoNotResume() { }
};
StreamingLexer<ICOState, 32> mLexer; // The lexer.
RefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
RefPtr<SourceBuffer> mContainedSourceBuffer; // SourceBuffer for mContainedDecoder.
NotNull<RefPtr<IResumable>> mDoNotResume; // IResumable helper for SourceBuffer.
UniquePtr<uint8_t[]> mMaskBuffer; // A temporary buffer for the alpha mask.
char mBIHraw[bmp::InfoHeaderLength::WIN_ICO]; // The bitmap information header.
IconDirEntry mDirEntry; // The dir entry for the selected resource.

Просмотреть файл

@ -27,13 +27,11 @@ nsIconDecoder::~nsIconDecoder()
{ }
Maybe<TerminalState>
nsIconDecoder::DoDecode(SourceBufferIterator& aIterator)
nsIconDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
MOZ_ASSERT(aIterator.Data());
MOZ_ASSERT(aIterator.Length() > 0);
return mLexer.Lex(aIterator.Data(), aIterator.Length(),
return mLexer.Lex(aIterator, aOnResume,
[=](State aState, const char* aData, size_t aLength) {
switch (aState) {
case State::HEADER:

Просмотреть файл

@ -37,7 +37,8 @@ class nsIconDecoder : public Decoder
public:
virtual ~nsIconDecoder();
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
private:
friend class DecoderFactory;

Просмотреть файл

@ -128,7 +128,7 @@ nsJPEGDecoder::SpeedHistogram()
return Telemetry::IMAGE_DECODE_SPEED_JPEG;
}
void
nsresult
nsJPEGDecoder::InitInternal()
{
mCMSMode = gfxPlatform::GetCMSMode();
@ -142,10 +142,9 @@ nsJPEGDecoder::InitInternal()
mErr.pub.error_exit = my_error_exit;
// Establish the setjmp return context for my_error_exit to use.
if (setjmp(mErr.setjmp_buffer)) {
// If we get here, the JPEG code has signaled an error.
// We need to clean up the JPEG object, close the input file, and return.
PostDecoderError(NS_ERROR_FAILURE);
return;
// If we get here, the JPEG code has signaled an error, and initialization
// has failed.
return NS_ERROR_FAILURE;
}
// Step 1: allocate and initialize JPEG decompression object
@ -166,9 +165,11 @@ nsJPEGDecoder::InitInternal()
for (uint32_t m = 0; m < 16; m++) {
jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF);
}
return NS_OK;
}
void
nsresult
nsJPEGDecoder::FinishInternal()
{
// If we're not in any sort of error case, force our state to JPEG_DONE.
@ -177,16 +178,16 @@ nsJPEGDecoder::FinishInternal()
!IsMetadataDecode()) {
mState = JPEG_DONE;
}
return NS_OK;
}
Maybe<TerminalState>
nsJPEGDecoder::DoDecode(SourceBufferIterator& aIterator)
nsJPEGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
MOZ_ASSERT(aIterator.Data());
MOZ_ASSERT(aIterator.Length() > 0);
return mLexer.Lex(aIterator.Data(), aIterator.Length(),
return mLexer.Lex(aIterator, aOnResume,
[=](State aState, const char* aData, size_t aLength) {
switch (aState) {
case State::JPEG_DATA:
@ -217,7 +218,6 @@ nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
("} (setjmp returned NS_ERROR_FAILURE)"));
} else {
// Error for another reason. (Possibly OOM.)
PostDecoderError(error_code);
mState = JPEG_ERROR;
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
("} (setjmp returned an error)"));
@ -301,7 +301,6 @@ nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
break;
default:
mState = JPEG_ERROR;
PostDataError();
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
("} (unknown colorpsace (1))"));
return Transition::TerminateFailure();
@ -318,7 +317,6 @@ nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
break;
default:
mState = JPEG_ERROR;
PostDataError();
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
("} (unknown colorpsace (2))"));
return Transition::TerminateFailure();
@ -377,7 +375,6 @@ nsJPEGDecoder::ReadJPEGData(const char* aData, size_t aLength)
break;
default:
mState = JPEG_ERROR;
PostDataError();
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
("} (unknown colorpsace (3))"));
return Transition::TerminateFailure();

Просмотреть файл

@ -57,9 +57,10 @@ public:
mSampleSize = aSampleSize;
}
virtual void InitInternal() override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator) override;
virtual void FinishInternal() override;
nsresult InitInternal() override;
Maybe<TerminalState> DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) override;
nsresult FinishInternal() override;
virtual Telemetry::ID SpeedHistogram() override;
void NotifyDone();

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше