зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
Коммит
a0992595d6
2
CLOBBER
2
CLOBBER
|
@ -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();
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче