mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
Merge m-c to b2g-inbound
This commit is contained in:
commit
a8c60dba60
@ -474,7 +474,7 @@ HTMLTextFieldAccessible::GetEditor() const
|
||||
// nsGenericHTMLElement::GetEditor has a security check.
|
||||
// Make sure we're not restricted by the permissions of
|
||||
// whatever script is currently running.
|
||||
mozilla::dom::AutoSystemCaller asc;
|
||||
mozilla::dom::AutoNoJSAPI nojsapi;
|
||||
|
||||
nsCOMPtr<nsIEditor> editor;
|
||||
editableElt->GetEditor(getter_AddRefs(editor));
|
||||
|
@ -1,10 +1,11 @@
|
||||
/* 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";
|
||||
|
||||
var self = require("sdk/self");
|
||||
var panels = require("sdk/panel");
|
||||
var widgets = require("sdk/widget");
|
||||
var { Panel } = require("sdk/panel");
|
||||
var { ToggleButton } = require("sdk/ui");
|
||||
|
||||
function replaceMom(html) {
|
||||
return html.replace("World", "Mom");
|
||||
@ -21,20 +22,18 @@ exports.main = function(options, callbacks) {
|
||||
helloHTML = replaceMom(helloHTML);
|
||||
|
||||
// ... and then create a panel that displays it.
|
||||
var myPanel = panels.Panel({
|
||||
contentURL: "data:text/html," + helloHTML
|
||||
var myPanel = Panel({
|
||||
contentURL: "data:text/html," + helloHTML,
|
||||
onHide: handleHide
|
||||
});
|
||||
|
||||
// Load the URL of the sample image.
|
||||
var iconURL = self.data.url("mom.png");
|
||||
|
||||
// Create a widget that displays the image. We'll attach the panel to it.
|
||||
// When you click the widget, the panel will pop up.
|
||||
widgets.Widget({
|
||||
var button = ToggleButton({
|
||||
id: "test-widget",
|
||||
label: "Mom",
|
||||
contentURL: iconURL,
|
||||
panel: myPanel
|
||||
icon: './mom.png',
|
||||
onChange: handleChange
|
||||
});
|
||||
|
||||
// If you run cfx with --static-args='{"quitWhenDone":true}' this program
|
||||
@ -42,3 +41,13 @@ exports.main = function(options, callbacks) {
|
||||
if (options.staticArgs.quitWhenDone)
|
||||
callbacks.quit();
|
||||
}
|
||||
|
||||
function handleChange(state) {
|
||||
if (state.checked) {
|
||||
myPanel.show({ position: button });
|
||||
}
|
||||
}
|
||||
|
||||
function handleHide() {
|
||||
button.state('window', { checked: false });
|
||||
}
|
||||
|
@ -3,9 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// Disable tests below for now.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
|
||||
/*
|
||||
var m = require("main");
|
||||
var self = require("sdk/self");
|
||||
|
||||
@ -26,4 +23,3 @@ exports.testID = function(test) {
|
||||
test.assertEqual(self.data.url("sample.html"),
|
||||
"resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html");
|
||||
};
|
||||
*/
|
||||
|
@ -1,26 +1,34 @@
|
||||
/* 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";
|
||||
|
||||
var data = require("sdk/self").data;
|
||||
var { data } = require("sdk/self");
|
||||
var { ToggleButton } = require("sdk/ui");
|
||||
|
||||
var base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
|
||||
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
|
||||
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
|
||||
"bWRR9AAAAABJRU5ErkJggg%3D%3D";
|
||||
|
||||
var reddit_panel = require("sdk/panel").Panel({
|
||||
width: 240,
|
||||
height: 320,
|
||||
contentURL: "http://www.reddit.com/.mobile?keep_extension=True",
|
||||
contentScriptFile: [data.url("jquery-1.4.4.min.js"),
|
||||
data.url("panel.js")]
|
||||
data.url("panel.js")],
|
||||
onHide: handleHide
|
||||
});
|
||||
|
||||
reddit_panel.port.on("click", function(url) {
|
||||
require("sdk/tabs").open(url);
|
||||
});
|
||||
|
||||
require("sdk/widget").Widget({
|
||||
let button = ToggleButton({
|
||||
id: "open-reddit-btn",
|
||||
label: "Reddit",
|
||||
contentURL: "http://www.reddit.com/static/favicon.ico",
|
||||
panel: reddit_panel
|
||||
icon: base64png,
|
||||
onChange: handleChange
|
||||
});
|
||||
|
||||
exports.main = function(options, callbacks) {
|
||||
@ -29,3 +37,13 @@ exports.main = function(options, callbacks) {
|
||||
if (options.staticArgs.quitWhenDone)
|
||||
callbacks.quit();
|
||||
};
|
||||
|
||||
function handleChange(state) {
|
||||
if (state.checked) {
|
||||
reddit_panel.show({ position: button });
|
||||
}
|
||||
}
|
||||
|
||||
function handleHide() {
|
||||
button.state('window', { checked: false });
|
||||
}
|
||||
|
@ -3,9 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// Disable tests below for now.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
|
||||
/*
|
||||
var m = require("main");
|
||||
var self = require("sdk/self");
|
||||
|
||||
@ -23,4 +20,3 @@ exports.testMain = function(test) {
|
||||
exports.testData = function(test) {
|
||||
test.assert(self.data.load("panel.js").length > 0);
|
||||
};
|
||||
*/
|
||||
|
@ -107,7 +107,9 @@ const ContentWorker = Object.freeze({
|
||||
error: pipe.emit.bind(null, "console", "error"),
|
||||
debug: pipe.emit.bind(null, "console", "debug"),
|
||||
exception: pipe.emit.bind(null, "console", "exception"),
|
||||
trace: pipe.emit.bind(null, "console", "trace")
|
||||
trace: pipe.emit.bind(null, "console", "trace"),
|
||||
time: pipe.emit.bind(null, "console", "time"),
|
||||
timeEnd: pipe.emit.bind(null, "console", "timeEnd")
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -31,6 +31,9 @@ let detachFrom = method("detatchFrom");
|
||||
exports.detachFrom = detachFrom;
|
||||
|
||||
function attach(modification, target) {
|
||||
if (!modification)
|
||||
return;
|
||||
|
||||
let window = getTargetWindow(target);
|
||||
|
||||
attachTo(modification, window);
|
||||
@ -42,6 +45,9 @@ function attach(modification, target) {
|
||||
exports.attach = attach;
|
||||
|
||||
function detach(modification, target) {
|
||||
if (!modification)
|
||||
return;
|
||||
|
||||
if (target) {
|
||||
let window = getTargetWindow(target);
|
||||
detachFrom(modification, window);
|
||||
|
@ -67,3 +67,8 @@ function load(sandbox, uri) {
|
||||
}
|
||||
}
|
||||
exports.load = load;
|
||||
|
||||
/**
|
||||
* Forces the given `sandbox` to be freed immediately.
|
||||
*/
|
||||
exports.nuke = Cu.nukeSandbox
|
||||
|
@ -201,6 +201,10 @@ function createWorker (mod, window) {
|
||||
contentScript: mod.contentScript,
|
||||
contentScriptFile: mod.contentScriptFile,
|
||||
contentScriptOptions: mod.contentScriptOptions,
|
||||
// Bug 980468: Syntax errors from scripts can happen before the worker
|
||||
// can set up an error handler. They are per-mod rather than per-worker
|
||||
// so are best handled at the mod level.
|
||||
onError: (e) => emit(mod, 'error', e)
|
||||
});
|
||||
workers.set(mod, worker);
|
||||
pipe(worker, mod);
|
||||
|
@ -18,7 +18,7 @@ const { isPrivateBrowsingSupported } = require('./self');
|
||||
const { isWindowPBSupported } = require('./private-browsing/utils');
|
||||
const { Class } = require("./core/heritage");
|
||||
const { merge } = require("./util/object");
|
||||
const { WorkerHost, detach, attach, destroy } = require("./content/utils");
|
||||
const { WorkerHost } = require("./content/utils");
|
||||
const { Worker } = require("./content/worker");
|
||||
const { Disposable } = require("./core/disposable");
|
||||
const { WeakReference } = require('./core/reference');
|
||||
@ -34,6 +34,8 @@ const { getNodeView, getActiveView } = require("./view/core");
|
||||
const { isNil, isObject, isNumber } = require("./lang/type");
|
||||
const { getAttachEventType } = require("./content/utils");
|
||||
const { number, boolean, object } = require('./deprecated/api-utils');
|
||||
const { Style } = require("./stylesheet/style");
|
||||
const { attach, detach } = require("./content/mod");
|
||||
|
||||
let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
|
||||
some(value => isNumber(value) && !isNaN(value));
|
||||
@ -63,7 +65,16 @@ let displayContract = contract({
|
||||
position: position
|
||||
});
|
||||
|
||||
let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules));
|
||||
let panelContract = contract(merge({
|
||||
// contentStyle* / contentScript* are sharing the same validation constraints,
|
||||
// so they can be mostly reused, except for the messages.
|
||||
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
|
||||
msg: 'The `contentStyle` option must be a string or an array of strings.'
|
||||
}),
|
||||
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
|
||||
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
|
||||
})
|
||||
}, displayContract.rules, loaderContract.rules));
|
||||
|
||||
|
||||
function isDisposed(panel) !views.has(panel);
|
||||
@ -72,12 +83,13 @@ let panels = new WeakMap();
|
||||
let models = new WeakMap();
|
||||
let views = new WeakMap();
|
||||
let workers = new WeakMap();
|
||||
let styles = new WeakMap();
|
||||
|
||||
function viewFor(panel) views.get(panel)
|
||||
function modelFor(panel) models.get(panel)
|
||||
function panelFor(view) panels.get(view)
|
||||
function workerFor(panel) workers.get(panel)
|
||||
|
||||
const viewFor = (panel) => views.get(panel);
|
||||
const modelFor = (panel) => models.get(panel);
|
||||
const panelFor = (view) => panels.get(view);
|
||||
const workerFor = (panel) => workers.get(panel);
|
||||
const styleFor = (panel) => styles.get(panel);
|
||||
|
||||
// Utility function takes `panel` instance and makes sure it will be
|
||||
// automatically hidden as soon as other panel is shown.
|
||||
@ -125,6 +137,12 @@ const Panel = Class({
|
||||
}, panelContract(options));
|
||||
models.set(this, model);
|
||||
|
||||
if (model.contentStyle || model.contentStyleFile) {
|
||||
styles.set(this, Style({
|
||||
uri: model.contentStyleFile,
|
||||
source: model.contentStyle
|
||||
}));
|
||||
}
|
||||
|
||||
// Setup view
|
||||
let view = domPanel.make();
|
||||
@ -148,7 +166,8 @@ const Panel = Class({
|
||||
this.hide();
|
||||
off(this);
|
||||
|
||||
destroy(workerFor(this));
|
||||
workerFor(this).destroy();
|
||||
detach(styleFor(this));
|
||||
|
||||
domPanel.dispose(viewFor(this));
|
||||
|
||||
@ -177,7 +196,7 @@ const Panel = Class({
|
||||
domPanel.setURL(viewFor(this), model.contentURL);
|
||||
// Detach worker so that messages send will be queued until it's
|
||||
// reatached once panel content is ready.
|
||||
detach(workerFor(this));
|
||||
workerFor(this).detach();
|
||||
},
|
||||
|
||||
/* Public API: Panel.isShowing */
|
||||
@ -262,12 +281,25 @@ let hides = filter(panelEvents, ({type}) => type === "popuphidden");
|
||||
let ready = filter(panelEvents, ({type, target}) =>
|
||||
getAttachEventType(modelFor(panelFor(target))) === type);
|
||||
|
||||
// Styles should be always added as soon as possible, and doesn't makes them
|
||||
// depends on `contentScriptWhen`
|
||||
let start = filter(panelEvents, ({type}) => type === "document-element-inserted");
|
||||
|
||||
// Forward panel show / hide events to panel's own event listeners.
|
||||
on(shows, "data", ({target}) => emit(panelFor(target), "show"));
|
||||
|
||||
on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
|
||||
|
||||
on(ready, "data", function({target}) {
|
||||
let worker = workerFor(panelFor(target));
|
||||
attach(worker, domPanel.getContentDocument(target).defaultView);
|
||||
on(ready, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
let window = domPanel.getContentDocument(target).defaultView;
|
||||
|
||||
workerFor(panel).attach(window);
|
||||
});
|
||||
|
||||
on(start, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
let window = domPanel.getContentDocument(target).defaultView;
|
||||
|
||||
attach(styleFor(panel), window);
|
||||
});
|
||||
|
@ -109,17 +109,21 @@ exports.pathFor = function pathFor(id) {
|
||||
*/
|
||||
exports.platform = runtime.OS.toLowerCase();
|
||||
|
||||
const [, architecture, compiler] = runtime.XPCOMABI ?
|
||||
runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
|
||||
[, null, null];
|
||||
|
||||
/**
|
||||
* What processor architecture you're running on:
|
||||
* `'arm', 'ia32', or 'x64'`.
|
||||
*/
|
||||
exports.architecture = runtime.XPCOMABI.split('_')[0];
|
||||
exports.architecture = architecture;
|
||||
|
||||
/**
|
||||
* What compiler used for build:
|
||||
* `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
|
||||
*/
|
||||
exports.compiler = runtime.XPCOMABI.split('_')[1];
|
||||
exports.compiler = compiler;
|
||||
|
||||
/**
|
||||
* The application's build ID/date, for example "2004051604".
|
||||
|
@ -8,12 +8,13 @@ module.metadata = {
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const { Cc, Ci, Cu } = require('chrome');
|
||||
const { Unknown } = require('../platform/xpcom');
|
||||
const { Class } = require('../core/heritage');
|
||||
const { ns } = require('../core/namespace');
|
||||
const { addObserver, removeObserver, notifyObservers } =
|
||||
Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
|
||||
const unloadSubject = require('@loader/unload');
|
||||
|
||||
const Subject = Class({
|
||||
extends: Unknown,
|
||||
@ -94,6 +95,10 @@ function on(type, listener, strong) {
|
||||
let observer = Observer(listener);
|
||||
observers[type] = observer;
|
||||
addObserver(observer, type, weak);
|
||||
// WeakRef gymnastics to remove all alive observers on unload
|
||||
let ref = Cu.getWeakReference(observer);
|
||||
weakRefs.set(observer, ref);
|
||||
stillAlive.set(ref, type);
|
||||
}
|
||||
}
|
||||
exports.on = on;
|
||||
@ -120,6 +125,31 @@ function off(type, listener) {
|
||||
let observer = observers[type];
|
||||
delete observers[type];
|
||||
removeObserver(observer, type);
|
||||
stillAlive.delete(weakRefs.get(observer));
|
||||
}
|
||||
}
|
||||
exports.off = off;
|
||||
|
||||
// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115
|
||||
let weakRefs = new WeakMap();
|
||||
|
||||
// and we're out of beta, we're releasing on time!
|
||||
let stillAlive = new Map();
|
||||
|
||||
on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
|
||||
// using logic from ./unload, to avoid a circular module reference
|
||||
if (subject.wrappedJSObject === unloadSubject) {
|
||||
off('sdk:loader:destroy', onunload);
|
||||
|
||||
// don't bother
|
||||
if (reason === 'shutdown')
|
||||
return;
|
||||
|
||||
stillAlive.forEach( (type, ref) => {
|
||||
let observer = ref.get();
|
||||
if (observer)
|
||||
removeObserver(observer, type);
|
||||
})
|
||||
}
|
||||
// a strong reference
|
||||
}, true);
|
||||
|
@ -62,9 +62,11 @@ exports.LoaderWithHookedConsole = function (module, callback) {
|
||||
error: hook.bind("error"),
|
||||
debug: hook.bind("debug"),
|
||||
exception: hook.bind("exception"),
|
||||
time: hook.bind("time"),
|
||||
timeEnd: hook.bind("timeEnd"),
|
||||
__exposedProps__: {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw"
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
}
|
||||
}
|
||||
}),
|
||||
@ -105,9 +107,11 @@ exports.LoaderWithFilteredConsole = function (module, callback) {
|
||||
error: hook.bind("error"),
|
||||
debug: hook.bind("debug"),
|
||||
exception: hook.bind("exception"),
|
||||
time: hook.bind("time"),
|
||||
timeEnd: hook.bind("timeEnd"),
|
||||
__exposedProps__: {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw"
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ const { isLocalURL } = require('../../url');
|
||||
const { isNil, isObject, isString } = require('../../lang/type');
|
||||
const { required, either, string, boolean, object } = require('../../deprecated/api-utils');
|
||||
const { merge } = require('../../util/object');
|
||||
const { freeze } = Object;
|
||||
|
||||
function isIconSet(icons) {
|
||||
return Object.keys(icons).
|
||||
@ -16,6 +17,7 @@ function isIconSet(icons) {
|
||||
|
||||
let iconSet = {
|
||||
is: either(object, string),
|
||||
map: v => isObject(v) ? freeze(merge({}, v)) : v,
|
||||
ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
|
||||
msg: 'The option "icon" must be a local URL or an object with ' +
|
||||
'numeric keys / local URL values pair.'
|
||||
|
@ -253,9 +253,11 @@ const Sidebar = Class({
|
||||
remove(sidebars, this);
|
||||
|
||||
// stop tracking windows
|
||||
internals.tracker.unload();
|
||||
internals.tracker = null;
|
||||
if (internals.tracker) {
|
||||
internals.tracker.unload();
|
||||
}
|
||||
|
||||
internals.tracker = null;
|
||||
internals.windowNS = null;
|
||||
|
||||
views.delete(this);
|
||||
|
@ -382,8 +382,8 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest(
|
||||
let calls = [];
|
||||
function onMessage(type, msg) {
|
||||
assert.equal(type, msg,
|
||||
"console.xxx(\"xxx\"), i.e. message is equal to the " +
|
||||
"console method name we are calling");
|
||||
"console.xxx(\"xxx\"), i.e. message is equal to the " +
|
||||
"console method name we are calling");
|
||||
calls.push(msg);
|
||||
}
|
||||
|
||||
@ -391,19 +391,23 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest(
|
||||
let worker = loader.require("sdk/content/worker").Worker({
|
||||
window: browser.contentWindow,
|
||||
contentScript: "new " + function WorkerScope() {
|
||||
console.time("time");
|
||||
console.log("log");
|
||||
console.info("info");
|
||||
console.warn("warn");
|
||||
console.error("error");
|
||||
console.debug("debug");
|
||||
console.exception("exception");
|
||||
console.timeEnd("timeEnd");
|
||||
self.postMessage();
|
||||
},
|
||||
onMessage: function() {
|
||||
// Ensure that console methods are called in the same execution order
|
||||
const EXPECTED_CALLS = ["time", "log", "info", "warn", "error",
|
||||
"debug", "exception", "timeEnd"];
|
||||
assert.equal(JSON.stringify(calls),
|
||||
JSON.stringify(["log", "info", "warn", "error", "debug", "exception"]),
|
||||
"console has been called successfully, in the expected order");
|
||||
JSON.stringify(EXPECTED_CALLS),
|
||||
"console methods have been called successfully, in expected order");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
@ -957,7 +957,7 @@ exports.testPageModCss = function(assert, done) {
|
||||
'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{
|
||||
include: ["*", "data:*"],
|
||||
contentStyle: "div { height: 100px; }",
|
||||
contentStyleFile: data.url("pagemod-css-include-file.css")
|
||||
contentStyleFile: data.url("css-include-file.css")
|
||||
}],
|
||||
function(win, done) {
|
||||
let div = win.document.querySelector("div");
|
||||
@ -1531,4 +1531,32 @@ exports.testDetachOnUnload = function(assert, done) {
|
||||
})
|
||||
}
|
||||
|
||||
exports.testSyntaxErrorInContentScript = function(assert, done) {
|
||||
const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript";
|
||||
let hitError = null;
|
||||
let attached = false;
|
||||
|
||||
testPageMod(assert, done, url, [{
|
||||
include: url,
|
||||
contentScript: 'console.log(23',
|
||||
|
||||
onAttach: function() {
|
||||
attached = true;
|
||||
},
|
||||
|
||||
onError: function(e) {
|
||||
hitError = e;
|
||||
}
|
||||
}],
|
||||
|
||||
function(win, done) {
|
||||
assert.ok(attached, "The worker was attached.");
|
||||
assert.notStrictEqual(hitError, null, "The syntax error was reported.");
|
||||
if (hitError)
|
||||
assert.equal(hitError.name, "SyntaxError", "The error thrown should be a SyntaxError");
|
||||
done();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
@ -25,6 +25,8 @@ const { URL } = require('sdk/url');
|
||||
const fixtures = require('./fixtures')
|
||||
|
||||
const SVG_URL = fixtures.url('mofo_logo.SVG');
|
||||
const CSS_URL = fixtures.url('css-include-file.css');
|
||||
|
||||
const Isolate = fn => '(' + fn + ')()';
|
||||
|
||||
function ignorePassingDOMNodeWarning(type, message) {
|
||||
@ -974,6 +976,88 @@ exports['test panel can be constructed without any arguments'] = function (asser
|
||||
assert.ok(true, "Creating a panel with no arguments does not throw");
|
||||
};
|
||||
|
||||
exports['test panel CSS'] = function(assert, done) {
|
||||
const loader = Loader(module);
|
||||
const { Panel } = loader.require('sdk/panel');
|
||||
|
||||
const { getActiveView } = loader.require('sdk/view/core');
|
||||
|
||||
const getContentWindow = panel =>
|
||||
getActiveView(panel).querySelector('iframe').contentWindow;
|
||||
|
||||
let panel = Panel({
|
||||
contentURL: 'data:text/html;charset=utf-8,' +
|
||||
'<div style="background: silver">css test</div>',
|
||||
contentStyle: 'div { height: 100px; }',
|
||||
contentStyleFile: CSS_URL,
|
||||
onShow: () => {
|
||||
ready(getContentWindow(panel)).then(({ document }) => {
|
||||
let div = document.querySelector('div');
|
||||
|
||||
assert.equal(div.clientHeight, 100, 'Panel contentStyle worked');
|
||||
assert.equal(div.offsetHeight, 120, 'Panel contentStyleFile worked');
|
||||
|
||||
loader.unload();
|
||||
done();
|
||||
}).then(null, assert.fail);
|
||||
}
|
||||
});
|
||||
|
||||
panel.show();
|
||||
};
|
||||
|
||||
exports['test panel CSS list'] = function(assert, done) {
|
||||
const loader = Loader(module);
|
||||
const { Panel } = loader.require('sdk/panel');
|
||||
|
||||
const { getActiveView } = loader.require('sdk/view/core');
|
||||
|
||||
const getContentWindow = panel =>
|
||||
getActiveView(panel).querySelector('iframe').contentWindow;
|
||||
|
||||
let panel = Panel({
|
||||
contentURL: 'data:text/html;charset=utf-8,' +
|
||||
'<div style="width:320px; max-width: 480px!important">css test</div>',
|
||||
contentStyleFile: [
|
||||
// Highlight evaluation order in this list
|
||||
"data:text/css;charset=utf-8,div { border: 1px solid black; }",
|
||||
"data:text/css;charset=utf-8,div { border: 10px solid black; }",
|
||||
// Highlight evaluation order between contentStylesheet & contentStylesheetFile
|
||||
"data:text/css;charset=utf-8s,div { height: 1000px; }",
|
||||
// Highlight precedence between the author and user style sheet
|
||||
"data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}",
|
||||
],
|
||||
contentStyle: [
|
||||
"div { height: 10px; }",
|
||||
"div { height: 100px; }"
|
||||
],
|
||||
onShow: () => {
|
||||
ready(getContentWindow(panel)).then(({ window, document }) => {
|
||||
let div = document.querySelector('div');
|
||||
let style = window.getComputedStyle(div);
|
||||
|
||||
assert.equal(div.clientHeight, 100,
|
||||
'Panel contentStyle list is evaluated after contentStyleFile');
|
||||
|
||||
assert.equal(div.offsetHeight, 120,
|
||||
'Panel contentStyleFile list works');
|
||||
|
||||
assert.equal(style.width, '320px',
|
||||
'add-on author/page author stylesheet precedence works');
|
||||
|
||||
assert.equal(style.maxWidth, '480px',
|
||||
'add-on author/page author stylesheet !important precedence works');
|
||||
|
||||
loader.unload();
|
||||
done();
|
||||
}).then(null, assert.fail);
|
||||
}
|
||||
});
|
||||
|
||||
panel.show();
|
||||
};
|
||||
|
||||
|
||||
if (isWindowPBSupported) {
|
||||
exports.testGetWindow = function(assert, done) {
|
||||
let activeWindow = getMostRecentBrowserWindow();
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
const { sandbox, load, evaluate } = require('sdk/loader/sandbox');
|
||||
const { sandbox, load, evaluate, nuke } = require('sdk/loader/sandbox');
|
||||
const xulApp = require("sdk/system/xul-app");
|
||||
const fixturesURI = module.uri.split('test-sandbox.js')[0] + 'fixtures/';
|
||||
|
||||
@ -137,4 +137,30 @@ exports['test metadata'] = function(assert) {
|
||||
let self = require('sdk/self');
|
||||
}
|
||||
|
||||
exports['test nuke sandbox'] = function(assert) {
|
||||
|
||||
let fixture = sandbox('http://example.com');
|
||||
fixture.foo = 'foo';
|
||||
|
||||
let ref = evaluate(fixture, 'let a = {bar: "bar"}; a');
|
||||
|
||||
nuke(fixture);
|
||||
|
||||
assert.ok(Cu.isDeadWrapper(fixture), 'sandbox should be dead');
|
||||
|
||||
assert.throws(
|
||||
() => fixture.foo,
|
||||
/can't access dead object/,
|
||||
'property of nuked sandbox should not be accessible'
|
||||
);
|
||||
|
||||
assert.ok(Cu.isDeadWrapper(ref), 'ref to object from sandbox should be dead');
|
||||
|
||||
assert.throws(
|
||||
() => ref.bar,
|
||||
/can't access dead object/,
|
||||
'object from nuked sandbox should not be alive'
|
||||
);
|
||||
}
|
||||
|
||||
require('test').run(exports);
|
||||
|
@ -119,6 +119,30 @@ exports["test listeners are GC-ed"] = function(assert, done) {
|
||||
});
|
||||
};
|
||||
|
||||
exports["test alive listeners are removed on unload"] = function(assert) {
|
||||
let receivedFromWeak = [];
|
||||
let receivedFromStrong = [];
|
||||
let loader = Loader(module);
|
||||
let events = loader.require('sdk/system/events');
|
||||
|
||||
let type = 'test-alive-listeners-are-removed';
|
||||
const handler = (event) => receivedFromStrong.push(event);
|
||||
const weakHandler = (event) => receivedFromWeak.push(event);
|
||||
|
||||
events.on(type, handler, true);
|
||||
events.on(type, weakHandler);
|
||||
|
||||
events.emit(type, { data: 1 });
|
||||
assert.equal(receivedFromStrong.length, 1, "strong listener invoked");
|
||||
assert.equal(receivedFromWeak.length, 1, "weak listener invoked");
|
||||
|
||||
loader.unload();
|
||||
events.emit(type, { data: 2 });
|
||||
|
||||
assert.equal(receivedFromWeak.length, 1, "weak listener was removed");
|
||||
assert.equal(receivedFromStrong.length, 1, "strong listener was removed");
|
||||
};
|
||||
|
||||
exports["test handle nsIObserverService notifications"] = function(assert) {
|
||||
let ios = Cc['@mozilla.org/network/io-service;1']
|
||||
.getService(Ci.nsIIOService);
|
||||
|
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var runtime = require("sdk/system/runtime");
|
||||
const runtime = require("sdk/system/runtime");
|
||||
|
||||
exports["test system runtime"] = function(assert) {
|
||||
assert.equal(typeof(runtime.inSafeMode), "boolean",
|
||||
@ -14,7 +14,7 @@ exports["test system runtime"] = function(assert) {
|
||||
"runtime.processType is a number");
|
||||
assert.equal(typeof(runtime.widgetToolkit), "string",
|
||||
"runtime.widgetToolkit is string");
|
||||
var XPCOMABI = typeof(runtime.XPCOMABI);
|
||||
const XPCOMABI = runtime.XPCOMABI;
|
||||
assert.ok(XPCOMABI === null || typeof(XPCOMABI) === "string",
|
||||
"runtime.XPCOMABI is string or null if not supported by platform");
|
||||
};
|
||||
|
37
addon-sdk/source/test/test-system.js
Normal file
37
addon-sdk/source/test/test-system.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* 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 runtime = require("sdk/system/runtime");
|
||||
const system = require("sdk/system");
|
||||
|
||||
exports["test system architecture and compiler"] = function(assert) {
|
||||
|
||||
if (system.architecture !== null) {
|
||||
assert.equal(
|
||||
runtime.XPCOMABI.indexOf(system.architecture), 0,
|
||||
"system.architecture is starting substring of runtime.XPCOMABI"
|
||||
);
|
||||
}
|
||||
|
||||
if (system.compiler !== null) {
|
||||
assert.equal(
|
||||
runtime.XPCOMABI.indexOf(system.compiler),
|
||||
runtime.XPCOMABI.length - system.compiler.length,
|
||||
"system.compiler is trailing substring of runtime.XPCOMABI"
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
system.architecture === null || typeof(system.architecture) === "string",
|
||||
"system.architecture is string or null if not supported by platform"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
system.compiler === null || typeof(system.compiler) === "string",
|
||||
"system.compiler is string or null if not supported by platform"
|
||||
);
|
||||
};
|
||||
|
||||
require("test").run(exports);
|
@ -835,6 +835,44 @@ exports['test button state are snapshot'] = function(assert) {
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports['test button icon object is a snapshot'] = function(assert) {
|
||||
let loader = Loader(module);
|
||||
let { ActionButton } = loader.require('sdk/ui');
|
||||
|
||||
let icon = {
|
||||
'16': './foo.png'
|
||||
};
|
||||
|
||||
let button = ActionButton({
|
||||
id: 'my-button-17',
|
||||
label: 'my button',
|
||||
icon: icon
|
||||
});
|
||||
|
||||
assert.deepEqual(button.icon, icon,
|
||||
'button.icon has the same properties of the object set in the constructor');
|
||||
|
||||
assert.notEqual(button.icon, icon,
|
||||
'button.icon is not the same object of the object set in the constructor');
|
||||
|
||||
assert.throws(
|
||||
() => button.icon[16] = './bar.png',
|
||||
/16 is read-only/,
|
||||
'properties of button.icon are ready-only'
|
||||
);
|
||||
|
||||
let newIcon = {'16': './bar.png'};
|
||||
button.icon = newIcon;
|
||||
|
||||
assert.deepEqual(button.icon, newIcon,
|
||||
'button.icon has the same properties of the object set');
|
||||
|
||||
assert.notEqual(button.icon, newIcon,
|
||||
'button.icon is not the same object of the object set');
|
||||
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports['test button after destroy'] = function(assert) {
|
||||
let loader = Loader(module);
|
||||
let { ActionButton } = loader.require('sdk/ui');
|
||||
|
@ -844,6 +844,44 @@ exports['test button state are snapshot'] = function(assert) {
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports['test button icon object is a snapshot'] = function(assert) {
|
||||
let loader = Loader(module);
|
||||
let { ToggleButton } = loader.require('sdk/ui');
|
||||
|
||||
let icon = {
|
||||
'16': './foo.png'
|
||||
};
|
||||
|
||||
let button = ToggleButton({
|
||||
id: 'my-button-17',
|
||||
label: 'my button',
|
||||
icon: icon
|
||||
});
|
||||
|
||||
assert.deepEqual(button.icon, icon,
|
||||
'button.icon has the same properties of the object set in the constructor');
|
||||
|
||||
assert.notEqual(button.icon, icon,
|
||||
'button.icon is not the same object of the object set in the constructor');
|
||||
|
||||
assert.throws(
|
||||
() => button.icon[16] = './bar.png',
|
||||
/16 is read-only/,
|
||||
'properties of button.icon are ready-only'
|
||||
);
|
||||
|
||||
let newIcon = {'16': './bar.png'};
|
||||
button.icon = newIcon;
|
||||
|
||||
assert.deepEqual(button.icon, newIcon,
|
||||
'button.icon has the same properties of the object set');
|
||||
|
||||
assert.notEqual(button.icon, newIcon,
|
||||
'button.icon is not the same object of the object set');
|
||||
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports['test button after destroy'] = function(assert) {
|
||||
let loader = Loader(module);
|
||||
let { ToggleButton } = loader.require('sdk/ui');
|
||||
|
@ -274,6 +274,7 @@ pref("ui.dragThresholdY", 25);
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
#ifndef MOZ_WIDGET_GONK
|
||||
pref("dom.ipc.tabs.disabled", true);
|
||||
pref("layers.acceleration.disabled", true);
|
||||
pref("layers.offmainthreadcomposition.async-animations", false);
|
||||
pref("layers.async-video.enabled", false);
|
||||
#else
|
||||
|
@ -456,6 +456,8 @@
|
||||
@BINPATH@/components/nsPlacesDBFlush.js
|
||||
@BINPATH@/components/nsPlacesAutoComplete.manifest
|
||||
@BINPATH@/components/nsPlacesAutoComplete.js
|
||||
@BINPATH@/components/UnifiedComplete.manifest
|
||||
@BINPATH@/components/UnifiedComplete.js
|
||||
@BINPATH@/components/nsPlacesExpiration.js
|
||||
@BINPATH@/components/PlacesProtocolHandler.js
|
||||
@BINPATH@/components/PlacesCategoriesStarter.js
|
||||
|
@ -122,7 +122,6 @@ tabbrowser {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tab-close-button,
|
||||
.tab-background {
|
||||
/* Explicitly set the visibility to override the value (collapsed)
|
||||
* we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
|
||||
@ -131,15 +130,28 @@ tabbrowser {
|
||||
transition: visibility 0ms 25ms;
|
||||
}
|
||||
|
||||
.tab-close-button:not([fadein]):not([pinned]),
|
||||
.tab-background:not([fadein]):not([pinned]) {
|
||||
visibility: hidden;
|
||||
/* Closing tabs are hidden without a delay. */
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
|
||||
.tab-close-button,
|
||||
.tab-label {
|
||||
/* Explicitly set the visibility to override the value (collapsed)
|
||||
* we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
|
||||
visibility: visible;
|
||||
transition: opacity 70ms 230ms,
|
||||
visibility 0ms 230ms;
|
||||
}
|
||||
|
||||
.tab-close-button:not([fadein]):not([pinned]),
|
||||
.tab-label:not([fadein]):not([pinned]) {
|
||||
visibility: collapse;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.tab-throbber:not([fadein]):not([pinned]),
|
||||
.tab-label:not([fadein]):not([pinned]),
|
||||
.tab-icon-image:not([fadein]):not([pinned]) {
|
||||
display: none;
|
||||
}
|
||||
|
@ -81,6 +81,10 @@
|
||||
Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
|
||||
.getService(Components.interfaces.mozIPlacesAutoComplete);
|
||||
</field>
|
||||
<field name="_unifiedComplete" readonly="true">
|
||||
Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
|
||||
.getService(Components.interfaces.mozIPlacesAutoComplete);
|
||||
</field>
|
||||
<field name="mTabBox" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
|
||||
</field>
|
||||
@ -744,8 +748,10 @@
|
||||
}
|
||||
|
||||
let autocomplete = this.mTabBrowser._placesAutocomplete;
|
||||
let unifiedComplete = this.mTabBrowser._unifiedComplete;
|
||||
if (this.mBrowser.registeredOpenURI) {
|
||||
autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
|
||||
unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
|
||||
delete this.mBrowser.registeredOpenURI;
|
||||
}
|
||||
// Tabs in private windows aren't registered as "Open" so
|
||||
@ -754,6 +760,7 @@
|
||||
(!PrivateBrowsingUtils.isWindowPrivate(window) ||
|
||||
PrivateBrowsingUtils.permanentPrivateBrowsing)) {
|
||||
autocomplete.registerOpenPage(aLocation);
|
||||
unifiedComplete.registerOpenPage(aLocation);
|
||||
this.mBrowser.registeredOpenURI = aLocation;
|
||||
}
|
||||
}
|
||||
@ -1672,12 +1679,6 @@
|
||||
|
||||
// kick the animation off
|
||||
t.setAttribute("fadein", "true");
|
||||
|
||||
// This call to adjustTabstrip is redundant but needed so that
|
||||
// when opening a second tab, the first tab's close buttons
|
||||
// appears immediately rather than when the transition ends.
|
||||
if (this.tabs.length - this._removingTabs.length == 2)
|
||||
this.tabContainer.adjustTabstrip();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
@ -1863,13 +1864,6 @@
|
||||
aTab.style.maxWidth = ""; // ensure that fade-out transition happens
|
||||
aTab.removeAttribute("fadein");
|
||||
|
||||
if (this.tabs.length - this._removingTabs.length == 1) {
|
||||
// The second tab just got closed and we will end up with a single
|
||||
// one. Remove the first tab's close button immediately (if needed)
|
||||
// rather than after the tabclose animation ends.
|
||||
this.tabContainer.adjustTabstrip();
|
||||
}
|
||||
|
||||
setTimeout(function (tab, tabbrowser) {
|
||||
if (tab.parentNode &&
|
||||
window.getComputedStyle(tab).maxWidth == "0.1px") {
|
||||
@ -1969,6 +1963,7 @@
|
||||
|
||||
if (browser.registeredOpenURI && !aTabWillBeMoved) {
|
||||
this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
|
||||
this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
|
||||
delete browser.registeredOpenURI;
|
||||
}
|
||||
|
||||
@ -2295,6 +2290,7 @@
|
||||
// If the current URI is registered as open remove it from the list.
|
||||
if (aOurBrowser.registeredOpenURI) {
|
||||
this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
|
||||
this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
|
||||
delete aOurBrowser.registeredOpenURI;
|
||||
}
|
||||
|
||||
@ -3104,6 +3100,7 @@
|
||||
let browser = this.getBrowserAtIndex(i);
|
||||
if (browser.registeredOpenURI) {
|
||||
this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
|
||||
this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
|
||||
delete browser.registeredOpenURI;
|
||||
}
|
||||
browser.webProgress.removeProgressListener(this.mTabFilters[i]);
|
||||
|
@ -68,6 +68,12 @@
|
||||
this.inputField.addEventListener("overflow", this, false);
|
||||
this.inputField.addEventListener("underflow", this, false);
|
||||
|
||||
try {
|
||||
if (this._prefs.getBoolPref("unifiedcomplete")) {
|
||||
this.setAttribute("autocompletesearch", "unifiedcomplete");
|
||||
}
|
||||
} catch (ex) {}
|
||||
|
||||
const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
var textBox = document.getAnonymousElementByAttribute(this,
|
||||
"anonid", "textbox-input-box");
|
||||
@ -599,6 +605,14 @@
|
||||
case "trimURLs":
|
||||
this._mayTrimURLs = this._prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "unifiedcomplete":
|
||||
let useUnifiedComplete = false;
|
||||
try {
|
||||
useUnifiedComplete = this._prefs.getBoolPref(aData);
|
||||
} catch (ex) {}
|
||||
this.setAttribute("autocompletesearch",
|
||||
useUnifiedComplete ? "unifiedcomplete"
|
||||
: "urlinline history");
|
||||
}
|
||||
}
|
||||
]]></body>
|
||||
|
@ -386,6 +386,13 @@ let CustomizableUIInternal = {
|
||||
gPlacements.set(aName, placements);
|
||||
}
|
||||
gFuturePlacements.delete(aName);
|
||||
let existingAreaNodes = gBuildAreas.get(aName);
|
||||
if (existingAreaNodes) {
|
||||
for (let areaNode of existingAreaNodes) {
|
||||
this.notifyListeners("onAreaNodeUnregistered", aName, areaNode.customizationTarget,
|
||||
CustomizableUI.REASON_AREA_UNREGISTERED);
|
||||
}
|
||||
}
|
||||
gBuildAreas.delete(aName);
|
||||
} finally {
|
||||
this.endBatchUpdate(true);
|
||||
@ -458,6 +465,7 @@ let CustomizableUIInternal = {
|
||||
if (gDirtyAreaCache.has(area)) {
|
||||
this.buildArea(area, placements, aToolbar);
|
||||
}
|
||||
this.notifyListeners("onAreaNodeRegistered", area, aToolbar.customizationTarget);
|
||||
aToolbar.setAttribute("currentset", placements.join(","));
|
||||
} finally {
|
||||
this.endBatchUpdate();
|
||||
@ -699,6 +707,8 @@ let CustomizableUIInternal = {
|
||||
|
||||
let placements = gPlacements.get(CustomizableUI.AREA_PANEL);
|
||||
this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanelContents);
|
||||
this.notifyListeners("onAreaNodeRegistered", CustomizableUI.AREA_PANEL, aPanelContents);
|
||||
|
||||
for (let child of aPanelContents.children) {
|
||||
if (child.localName != "toolbarbutton") {
|
||||
if (child.localName == "toolbaritem") {
|
||||
@ -839,6 +849,8 @@ let CustomizableUIInternal = {
|
||||
let areaProperties = gAreas.get(areaId);
|
||||
for (let node of areaNodes) {
|
||||
if (node.ownerDocument == document) {
|
||||
this.notifyListeners("onAreaNodeUnregistered", areaId, node.customizationTarget,
|
||||
CustomizableUI.REASON_WINDOW_CLOSED);
|
||||
if (areaProperties.has("overflowable")) {
|
||||
node.overflowable.uninit();
|
||||
node.overflowable = null;
|
||||
@ -2502,6 +2514,17 @@ this.CustomizableUI = {
|
||||
*/
|
||||
get PANEL_COLUMN_COUNT() 3,
|
||||
|
||||
/**
|
||||
* Constant indicating the reason the event was fired was a window closing
|
||||
*/
|
||||
get REASON_WINDOW_CLOSED() "window-closed",
|
||||
/**
|
||||
* Constant indicating the reason the event was fired was an area being
|
||||
* unregistered separately from window closing mechanics.
|
||||
*/
|
||||
get REASON_AREA_UNREGISTERED() "area-unregistered",
|
||||
|
||||
|
||||
/**
|
||||
* An iteratable property of windows managed by CustomizableUI.
|
||||
* Note that this can *only* be used as an iterator. ie:
|
||||
@ -2603,6 +2626,15 @@ this.CustomizableUI = {
|
||||
* - onWindowClosed(aWindow)
|
||||
* Fired when a window that has been managed by CustomizableUI has been
|
||||
* closed.
|
||||
* - onAreaNodeRegistered(aArea, aContainer)
|
||||
* Fired after an area node is first built when it is registered. This
|
||||
* is often when the window has opened, but in the case of add-ons,
|
||||
* could fire when the node has just been registered with CustomizableUI
|
||||
* after an add-on update or disable/enable sequence.
|
||||
* - onAreaNodeUnregistered(aArea, aContainer, aReason)
|
||||
* Fired when an area node is explicitly unregistered by an API caller,
|
||||
* or by a window closing. The aReason parameter indicates which of
|
||||
* these is the case.
|
||||
*/
|
||||
addListener: function(aListener) {
|
||||
CustomizableUIInternal.addListener(aListener);
|
||||
|
@ -236,11 +236,7 @@ CustomizeMode.prototype = {
|
||||
yield this._wrapToolbarItems();
|
||||
this.populatePalette();
|
||||
|
||||
this.visiblePalette.addEventListener("dragstart", this, true);
|
||||
this.visiblePalette.addEventListener("dragover", this, true);
|
||||
this.visiblePalette.addEventListener("dragexit", this, true);
|
||||
this.visiblePalette.addEventListener("drop", this, true);
|
||||
this.visiblePalette.addEventListener("dragend", this, true);
|
||||
this._addDragHandlers(this.visiblePalette);
|
||||
|
||||
window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
|
||||
|
||||
@ -402,11 +398,7 @@ CustomizeMode.prototype = {
|
||||
window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
|
||||
|
||||
DragPositionManager.stop();
|
||||
this.visiblePalette.removeEventListener("dragstart", this, true);
|
||||
this.visiblePalette.removeEventListener("dragover", this, true);
|
||||
this.visiblePalette.removeEventListener("dragexit", this, true);
|
||||
this.visiblePalette.removeEventListener("drop", this, true);
|
||||
this.visiblePalette.removeEventListener("dragend", this, true);
|
||||
this._removeDragHandlers(this.visiblePalette);
|
||||
|
||||
yield this._unwrapToolbarItems();
|
||||
|
||||
@ -900,11 +892,7 @@ CustomizeMode.prototype = {
|
||||
this.areas = [];
|
||||
for (let area of CustomizableUI.areas) {
|
||||
let target = CustomizableUI.getCustomizeTargetForArea(area, window);
|
||||
target.addEventListener("dragstart", this, true);
|
||||
target.addEventListener("dragover", this, true);
|
||||
target.addEventListener("dragexit", this, true);
|
||||
target.addEventListener("drop", this, true);
|
||||
target.addEventListener("dragend", this, true);
|
||||
this._addDragHandlers(target);
|
||||
for (let child of target.children) {
|
||||
if (this.isCustomizableItem(child)) {
|
||||
yield this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
|
||||
@ -915,6 +903,14 @@ CustomizeMode.prototype = {
|
||||
}.bind(this)).then(null, ERROR);
|
||||
},
|
||||
|
||||
_addDragHandlers: function(aTarget) {
|
||||
aTarget.addEventListener("dragstart", this, true);
|
||||
aTarget.addEventListener("dragover", this, true);
|
||||
aTarget.addEventListener("dragexit", this, true);
|
||||
aTarget.addEventListener("drop", this, true);
|
||||
aTarget.addEventListener("dragend", this, true);
|
||||
},
|
||||
|
||||
_wrapItemsInArea: function(target) {
|
||||
for (let child of target.children) {
|
||||
if (this.isCustomizableItem(child)) {
|
||||
@ -923,6 +919,14 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_removeDragHandlers: function(aTarget) {
|
||||
aTarget.removeEventListener("dragstart", this, true);
|
||||
aTarget.removeEventListener("dragover", this, true);
|
||||
aTarget.removeEventListener("dragexit", this, true);
|
||||
aTarget.removeEventListener("drop", this, true);
|
||||
aTarget.removeEventListener("dragend", this, true);
|
||||
},
|
||||
|
||||
_unwrapItemsInArea: function(target) {
|
||||
for (let toolbarItem of target.children) {
|
||||
if (this.isWrappedToolbarItem(toolbarItem)) {
|
||||
@ -939,11 +943,7 @@ CustomizeMode.prototype = {
|
||||
yield this.deferredUnwrapToolbarItem(toolbarItem);
|
||||
}
|
||||
}
|
||||
target.removeEventListener("dragstart", this, true);
|
||||
target.removeEventListener("dragover", this, true);
|
||||
target.removeEventListener("dragexit", this, true);
|
||||
target.removeEventListener("drop", this, true);
|
||||
target.removeEventListener("dragend", this, true);
|
||||
this._removeDragHandlers(target);
|
||||
}
|
||||
}.bind(this)).then(null, ERROR);
|
||||
},
|
||||
@ -1118,6 +1118,25 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
onAreaNodeRegistered: function(aArea, aContainer) {
|
||||
if (aContainer.ownerDocument == this.document) {
|
||||
this._wrapItemsInArea(aContainer);
|
||||
this._addDragHandlers(aContainer);
|
||||
DragPositionManager.add(this.window, aArea, aContainer);
|
||||
this.areas.push(aContainer);
|
||||
}
|
||||
},
|
||||
|
||||
onAreaNodeUnregistered: function(aArea, aContainer, aReason) {
|
||||
if (aContainer.ownerDocument == this.document && aReason == CustomizableUI.REASON_AREA_UNREGISTERED) {
|
||||
this._unwrapItemsInArea(aContainer);
|
||||
this._removeDragHandlers(aContainer);
|
||||
DragPositionManager.remove(this.window, aArea, aContainer);
|
||||
let index = this.areas.indexOf(aContainer);
|
||||
this.areas.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
_onUIChange: function() {
|
||||
this._changed = true;
|
||||
if (!this.resetting) {
|
||||
|
@ -393,6 +393,22 @@ let DragPositionManager = {
|
||||
}
|
||||
},
|
||||
|
||||
add: function(aWindow, aArea, aContainer) {
|
||||
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
|
||||
return;
|
||||
}
|
||||
|
||||
gManagers.set(aContainer, new AreaPositionManager(aContainer));
|
||||
},
|
||||
|
||||
remove: function(aWindow, aArea, aContainer) {
|
||||
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
|
||||
return;
|
||||
}
|
||||
|
||||
gManagers.delete(aContainer);
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
gManagers.clear();
|
||||
},
|
||||
|
@ -104,5 +104,6 @@ skip-if = os == "linux"
|
||||
[browser_987492_window_api.js]
|
||||
[browser_992747_toggle_noncustomizable_toolbar.js]
|
||||
[browser_993322_widget_notoolbar.js]
|
||||
[browser_995164_registerArea_during_customize_mode.js]
|
||||
[browser_bootstrapped_custom_toolbar.js]
|
||||
[browser_panel_toggle.js]
|
||||
|
@ -31,4 +31,6 @@ add_task(function*() {
|
||||
|
||||
CustomizableUI.destroyWidget(BUTTONID);
|
||||
CustomizableUI.unregisterArea(TOOLBARID, true);
|
||||
toolbar.remove();
|
||||
gAddedToolbars.clear();
|
||||
});
|
||||
|
@ -0,0 +1,113 @@
|
||||
/* 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 TOOLBARID = "test-toolbar-added-during-customize-mode";
|
||||
|
||||
add_task(function*() {
|
||||
yield startCustomizing();
|
||||
let toolbar = createToolbarWithPlacements(TOOLBARID, []);
|
||||
CustomizableUI.addWidgetToArea("sync-button", TOOLBARID);
|
||||
let syncButton = document.getElementById("sync-button");
|
||||
ok(syncButton, "Sync button should exist.");
|
||||
is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should be a wrapper.");
|
||||
|
||||
simulateItemDrag(syncButton, gNavToolbox.palette);
|
||||
ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette");
|
||||
ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette.");
|
||||
|
||||
simulateItemDrag(syncButton, toolbar);
|
||||
ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette");
|
||||
is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar");
|
||||
ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar.");
|
||||
|
||||
yield endCustomizing();
|
||||
isnot(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should not be a wrapper outside customize mode.");
|
||||
yield startCustomizing();
|
||||
|
||||
is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should be a wrapper back in customize mode.");
|
||||
|
||||
simulateItemDrag(syncButton, gNavToolbox.palette);
|
||||
ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette");
|
||||
ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette.");
|
||||
|
||||
ok(!CustomizableUI.inDefaultState, "Not in default state while toolbar is not collapsed yet.");
|
||||
setToolbarVisibility(toolbar, false);
|
||||
ok(CustomizableUI.inDefaultState, "In default state while toolbar is collapsed.");
|
||||
|
||||
setToolbarVisibility(toolbar, true);
|
||||
|
||||
info("Check that removing the area registration from within customize mode works");
|
||||
CustomizableUI.unregisterArea(TOOLBARID);
|
||||
ok(CustomizableUI.inDefaultState, "Now that the toolbar is no longer registered, should be in default state.");
|
||||
ok(!(new Set(gCustomizeMode.areas)).has(toolbar), "Toolbar shouldn't be known to customize mode.");
|
||||
|
||||
CustomizableUI.registerArea(TOOLBARID, {legacy: true, defaultPlacements: []});
|
||||
CustomizableUI.registerToolbarNode(toolbar, []);
|
||||
ok(!CustomizableUI.inDefaultState, "Now that the toolbar is registered again, should no longer be in default state.");
|
||||
ok((new Set(gCustomizeMode.areas)).has(toolbar), "Toolbar should be known to customize mode again.");
|
||||
|
||||
simulateItemDrag(syncButton, toolbar);
|
||||
ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette");
|
||||
is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar");
|
||||
ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar.");
|
||||
|
||||
let otherWin = yield openAndLoadWindow({}, true);
|
||||
let otherTB = otherWin.document.createElementNS(kNSXUL, "toolbar");
|
||||
otherTB.id = TOOLBARID;
|
||||
otherTB.setAttribute("customizable", "true");
|
||||
otherWin.gNavToolbox.appendChild(otherTB);
|
||||
ok(otherTB.querySelector("#sync-button"), "Sync button is on other toolbar, too.");
|
||||
|
||||
simulateItemDrag(syncButton, gNavToolbox.palette);
|
||||
ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette");
|
||||
ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette.");
|
||||
ok(!otherTB.querySelector("#sync-button"), "Sync button is in palette in other window, too.");
|
||||
|
||||
simulateItemDrag(syncButton, toolbar);
|
||||
ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette");
|
||||
is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar");
|
||||
ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar.");
|
||||
ok(otherTB.querySelector("#sync-button"), "Sync button is on other toolbar, too.");
|
||||
|
||||
let wasInformedCorrectlyOfAreaDisappearing = false;
|
||||
let listener = {
|
||||
onAreaNodeUnregistered: function(aArea, aNode, aReason) {
|
||||
if (aArea == TOOLBARID) {
|
||||
is(aNode, otherTB, "Should be informed about other toolbar");
|
||||
is(aReason, CustomizableUI.REASON_WINDOW_CLOSED, "Reason should be correct.");
|
||||
wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_WINDOW_CLOSED);
|
||||
}
|
||||
}
|
||||
};
|
||||
CustomizableUI.addListener(listener);
|
||||
yield promiseWindowClosed(otherWin);
|
||||
|
||||
ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about window closing.");
|
||||
CustomizableUI.removeListener(listener);
|
||||
// Closing the other window should not be counted against this window's customize mode:
|
||||
is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should still be a wrapper.");
|
||||
isnot(gCustomizeMode.areas.indexOf(toolbar), -1, "Toolbar should still be a customizable area for this customize mode instance.");
|
||||
|
||||
yield gCustomizeMode.reset();
|
||||
|
||||
yield endCustomizing();
|
||||
|
||||
wasInformedCorrectlyOfAreaDisappearing = false;
|
||||
listener = {
|
||||
onAreaNodeUnregistered: function(aArea, aNode, aReason) {
|
||||
if (aArea == TOOLBARID) {
|
||||
is(aNode, toolbar, "Should be informed about this window's toolbar");
|
||||
is(aReason, CustomizableUI.REASON_AREA_UNREGISTERED, "Reason for final removal should be correct.");
|
||||
wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_AREA_UNREGISTERED);
|
||||
}
|
||||
},
|
||||
}
|
||||
CustomizableUI.addListener(listener);
|
||||
removeCustomToolbars();
|
||||
ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about area being unregistered.");
|
||||
CustomizableUI.removeListener(listener);
|
||||
ok(CustomizableUI.inDefaultState, "Should be fine after exiting customize mode.");
|
||||
});
|
@ -145,6 +145,8 @@ skip-if = true
|
||||
[browser_590268.js]
|
||||
[browser_590563.js]
|
||||
[browser_595601-restore_hidden.js]
|
||||
[browser_597071.js]
|
||||
skip-if = true # Needs to be rewritten as Marionette test, bug 995916
|
||||
[browser_599909.js]
|
||||
[browser_600545.js]
|
||||
[browser_601955.js]
|
||||
@ -184,8 +186,6 @@ skip-if = true
|
||||
skip-if = true
|
||||
|
||||
# Disabled on OS X:
|
||||
[browser_597071.js]
|
||||
skip-if = os == "mac" || e10s
|
||||
[browser_625016.js]
|
||||
skip-if = os == "mac" || e10s
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
const stateBackup = ss.getBrowserState();
|
||||
const stateBackup = JSON.parse(ss.getBrowserState());
|
||||
const testState = {
|
||||
windows: [{
|
||||
tabs: [
|
||||
@ -76,8 +76,7 @@ function runNextTest() {
|
||||
});
|
||||
}
|
||||
else {
|
||||
ss.setBrowserState(stateBackup);
|
||||
finish();
|
||||
waitForBrowserState(stateBackup, finish);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ whitelist['nightly']['macosx-universal'] += [
|
||||
whitelist['nightly']['win32'] += [
|
||||
'. $topsrcdir/configs/mozilla2/win32/include/choose-make-flags',
|
||||
'mk_add_options MOZ_MAKE_FLAGS=-j1',
|
||||
'. "$topsrcdir/build/mozconfig.cache"',
|
||||
'if test "$IS_NIGHTLY" != ""; then',
|
||||
'ac_add_options --disable-auto-deps',
|
||||
'fi',
|
||||
|
@ -19,4 +19,6 @@ ac_add_options --enable-warnings-as-errors
|
||||
# Package js shell.
|
||||
export MOZ_PACKAGE_JSSHELL=1
|
||||
|
||||
. "$topsrcdir/build/mozconfig.cache"
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
@ -6,4 +6,6 @@ ac_add_options --enable-profiling
|
||||
# Nightlies only since this has a cost in performance
|
||||
ac_add_options --enable-js-diagnostics
|
||||
|
||||
. "$topsrcdir/build/mozconfig.cache"
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common.override"
|
||||
|
@ -4,5 +4,17 @@
|
||||
"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
|
||||
"algorithm": "sha512",
|
||||
"filename": "mozmake.exe"
|
||||
},
|
||||
{
|
||||
"size": 95,
|
||||
"digest": "258c35efc786841267f8d377943274bd3eb4bd81bed2186db57b98c1543f4c513faf4f7845daf66821ef3b555ea3c2133e0d980a5ef10154f6d24596ff3a5de5",
|
||||
"algorithm": "sha512",
|
||||
"filename": "setup.sh"
|
||||
},
|
||||
{
|
||||
"size": 160232,
|
||||
"digest": "8656c3fc2daa66839ec81a0edbd9759040a83c7a41c3e472d7f90508b80eefd008b87305dc8549b4ff6098dc33fe17fedc9b4eb76cf5307d5f22dae925c033db",
|
||||
"algorithm": "sha512",
|
||||
"filename": "sccache.tar.xz"
|
||||
}
|
||||
]
|
||||
|
@ -429,6 +429,8 @@
|
||||
@BINPATH@/components/nsTaggingService.js
|
||||
@BINPATH@/components/nsPlacesAutoComplete.manifest
|
||||
@BINPATH@/components/nsPlacesAutoComplete.js
|
||||
@BINPATH@/components/UnifiedComplete.manifest
|
||||
@BINPATH@/components/UnifiedComplete.js
|
||||
@BINPATH@/components/nsPlacesExpiration.js
|
||||
@BINPATH@/browser/components/PlacesProtocolHandler.js
|
||||
@BINPATH@/components/PlacesCategoriesStarter.js
|
||||
|
@ -88,7 +88,7 @@
|
||||
margin-top: @windowButtonMarginTop@;
|
||||
}
|
||||
|
||||
#main-window[customizing] > #titlebar {
|
||||
#main-window[customize-entered] > #titlebar {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
@ -2847,6 +2847,16 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
padding: 6px 0 4px;
|
||||
}
|
||||
|
||||
/* Background tabs:
|
||||
*
|
||||
* Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
|
||||
* of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
|
||||
* the titlebar. We don't need this in fullscreen since window dragging is not an issue there.
|
||||
*/
|
||||
#main-window[tabsintitlebar]:not([inFullscreen]) .tab-background-middle:not([selected=true]) {
|
||||
clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab Drag and Drop
|
||||
*/
|
||||
|
@ -226,20 +226,6 @@
|
||||
|
||||
/* End selected tab */
|
||||
|
||||
/* Background tabs */
|
||||
|
||||
/* Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
|
||||
of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
|
||||
the titlebar. We don't need this in fullscreen since window dragging is not an issue there. */
|
||||
%ifdef XP_MACOSX
|
||||
#main-window[tabsintitlebar][sizemode="maximized"] .tab-background-middle:not([selected=true]),
|
||||
%endif
|
||||
#main-window[tabsintitlebar]:not([sizemode="maximized"]):not([inFullscreen]) #toolbar-menubar:-moz-any([autohide="true"][inactive], :not([autohide])) + #TabsToolbar .tab-background-middle:not([selected=true]) {
|
||||
clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
|
||||
}
|
||||
|
||||
/* End background tabs */
|
||||
|
||||
/* new tab button border and gradient on hover */
|
||||
.tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
|
||||
.tabs-newtab-button:hover {
|
||||
|
@ -1902,6 +1902,16 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
|
||||
/* Background tabs:
|
||||
*
|
||||
* Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
|
||||
* of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
|
||||
* the titlebar. We don't need this in fullscreen since window dragging is not an issue there.
|
||||
*/
|
||||
#main-window[tabsintitlebar][sizemode=normal] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar .tab-background-middle:not([selected=true]) {
|
||||
clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
|
||||
}
|
||||
|
||||
/* Tab DnD indicator */
|
||||
.tab-drop-indicator {
|
||||
list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
|
||||
|
@ -11,6 +11,10 @@ EOF
|
||||
bucket=
|
||||
if test -z "$SCCACHE_DISABLE" -a -z "$no_tooltool"; then
|
||||
case "${branch}_${master}" in
|
||||
try_*scl1.mozilla.com*|try_*.scl3.mozilla.com*)
|
||||
bucket=mozilla-releng-ceph-cache-scl3-try
|
||||
mk_add_options "export SCCACHE_NO_HTTPS=1"
|
||||
;;
|
||||
try_*use1.mozilla.com*)
|
||||
bucket=mozilla-releng-s3-cache-us-east-1-try
|
||||
;;
|
||||
@ -36,4 +40,18 @@ else
|
||||
ac_add_options "--with-compiler-wrapper=python2.7 $topsrcdir/sccache/sccache.py"
|
||||
mk_add_options MOZ_PREFLIGHT+=build/sccache.mk
|
||||
mk_add_options MOZ_POSTFLIGHT+=build/sccache.mk
|
||||
case "$platform" in
|
||||
win*)
|
||||
# sccache supports a special flag to create depfiles.
|
||||
export _DEPEND_CFLAGS='-deps$(MDDEPDIR)/$(@F).pp'
|
||||
# Windows builds have a default wrapper that needs to be overridden
|
||||
mk_add_options "export CC_WRAPPER="
|
||||
mk_add_options "export CXX_WRAPPER="
|
||||
# For now, sccache doesn't support separate PDBs so force debug info to be
|
||||
# in object files.
|
||||
mk_add_options "export COMPILE_PDB_FLAG="
|
||||
mk_add_options "export HOST_PDB_FLAG="
|
||||
mk_add_options "export MOZ_DEBUG_FLAGS=-Z7"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
@ -82,6 +82,7 @@ included_inclnames_to_ignore = set([
|
||||
'unicode/udat.h', # ICU
|
||||
'unicode/udatpg.h', # ICU
|
||||
'unicode/uenum.h', # ICU
|
||||
'unicode/unorm.h', # ICU
|
||||
'unicode/unum.h', # ICU
|
||||
'unicode/ustring.h', # ICU
|
||||
'unicode/utypes.h', # ICU
|
||||
@ -200,7 +201,7 @@ class FileKind(object):
|
||||
if filename.endswith('.cpp'):
|
||||
return FileKind.CPP
|
||||
|
||||
if filename.endswith(('inlines.h', '-inl.h', 'Inlines.h')):
|
||||
if filename.endswith(('inlines.h', '-inl.h')):
|
||||
return FileKind.INL_H
|
||||
|
||||
if filename.endswith('.h'):
|
||||
|
@ -415,7 +415,7 @@ protected:
|
||||
mozilla::dom::ParentObject p(aNativeParent);
|
||||
// Note that mUseXBLScope is a no-op for chrome, and other places where we
|
||||
// don't use XBL scopes.
|
||||
p.mUseXBLScope = IsInAnonymousSubtree();
|
||||
p.mUseXBLScope = IsInAnonymousSubtree() && !IsAnonymousContentInSVGUseSubtree();
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -1012,6 +1012,9 @@ public:
|
||||
|
||||
bool IsInAnonymousSubtree() const;
|
||||
|
||||
// Note: This asserts |IsInAnonymousSubtree()|.
|
||||
bool IsAnonymousContentInSVGUseSubtree() const;
|
||||
|
||||
// True for native anonymous content and for XBL content if the binging
|
||||
// has chromeOnlyContent="true".
|
||||
bool ChromeOnlyAccess() const
|
||||
|
@ -375,6 +375,15 @@ nsINode::IsInAnonymousSubtree() const
|
||||
return AsContent()->IsInAnonymousSubtree();
|
||||
}
|
||||
|
||||
bool
|
||||
nsINode::IsAnonymousContentInSVGUseSubtree() const
|
||||
{
|
||||
MOZ_ASSERT(IsInAnonymousSubtree());
|
||||
nsIContent* parent = AsContent()->GetBindingParent();
|
||||
// Watch out for parentless native-anonymous subtrees.
|
||||
return parent && parent->IsSVG(nsGkAtoms::use);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsINode::GetParentNode(nsIDOMNode** aParentNode)
|
||||
{
|
||||
|
@ -466,14 +466,15 @@ nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
|
||||
mFrameCreateCalled = false;
|
||||
|
||||
// We need to make sure that our image request is deregistered.
|
||||
nsPresContext* presContext = GetFramePresContext();
|
||||
if (mCurrentRequest) {
|
||||
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
|
||||
nsLayoutUtils::DeregisterImageRequest(presContext,
|
||||
mCurrentRequest,
|
||||
&mCurrentRequestRegistered);
|
||||
}
|
||||
|
||||
if (mPendingRequest) {
|
||||
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
|
||||
nsLayoutUtils::DeregisterImageRequest(presContext,
|
||||
mPendingRequest,
|
||||
&mPendingRequestRegistered);
|
||||
}
|
||||
@ -481,6 +482,11 @@ nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
|
||||
UntrackImage(mCurrentRequest);
|
||||
UntrackImage(mPendingRequest);
|
||||
|
||||
nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
|
||||
if (presShell) {
|
||||
presShell->RemoveImageFromVisibleList(this);
|
||||
}
|
||||
|
||||
if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
|
||||
// We assume all images in popups are visible, so this decrement balances
|
||||
// out the increment in FrameCreated above.
|
||||
|
@ -3635,7 +3635,6 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x,
|
||||
return;
|
||||
}
|
||||
nsRefPtr<gfxContext> thebes;
|
||||
nsRefPtr<gfxASurface> drawSurf;
|
||||
RefPtr<DrawTarget> drawDT;
|
||||
if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget)) {
|
||||
thebes = new gfxContext(mTarget);
|
||||
@ -3656,34 +3655,15 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x,
|
||||
|
||||
nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
|
||||
unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
|
||||
if (drawSurf || drawDT) {
|
||||
RefPtr<SourceSurface> source;
|
||||
if (drawDT) {
|
||||
RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
|
||||
RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
|
||||
|
||||
if (drawSurf) {
|
||||
gfxIntSize size = drawSurf->GetSize();
|
||||
|
||||
drawSurf->SetDeviceOffset(gfxPoint(0, 0));
|
||||
nsRefPtr<gfxImageSurface> img = drawSurf->GetAsReadableARGB32ImageSurface();
|
||||
if (!img || !img->Data()) {
|
||||
error.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
source =
|
||||
mTarget->CreateSourceSurfaceFromData(img->Data(),
|
||||
IntSize(size.width, size.height),
|
||||
img->Stride(),
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
} else {
|
||||
RefPtr<SourceSurface> snapshot = drawDT->Snapshot();
|
||||
RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
|
||||
|
||||
source =
|
||||
mTarget->CreateSourceSurfaceFromData(data->GetData(),
|
||||
data->GetSize(),
|
||||
data->Stride(),
|
||||
data->GetFormat());
|
||||
}
|
||||
RefPtr<SourceSurface> source =
|
||||
mTarget->CreateSourceSurfaceFromData(data->GetData(),
|
||||
data->GetSize(),
|
||||
data->Stride(),
|
||||
data->GetFormat());
|
||||
|
||||
if (!source) {
|
||||
error.Throw(NS_ERROR_FAILURE);
|
||||
@ -3694,7 +3674,8 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x,
|
||||
mgfx::Rect sourceRect(0, 0, sw, sh);
|
||||
mTarget->DrawSurface(source, destRect, sourceRect,
|
||||
DrawSurfaceOptions(mgfx::Filter::POINT),
|
||||
DrawOptions(1.0f, CompositionOp::OP_SOURCE, AntialiasMode::NONE));
|
||||
DrawOptions(1.0f, CompositionOp::OP_OVER,
|
||||
AntialiasMode::NONE));
|
||||
mTarget->Flush();
|
||||
} else {
|
||||
mTarget->SetTransform(matrix);
|
||||
|
@ -2,3 +2,5 @@
|
||||
support-files = nonchrome_webgl_debug_renderer_info.html
|
||||
|
||||
[test_webgl_debug_renderer_info.html]
|
||||
[test_drawWindow_widget_layers.html]
|
||||
support-files = ../file_drawWindow_source.html ../file_drawWindow_common.js
|
||||
|
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for canvas drawWindow</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<script type="application/javascript" src="file_drawWindow_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.addEventListener("load", openSourceWindow, false);
|
||||
|
||||
var sourceWindow;
|
||||
|
||||
function openSourceWindow(event) {
|
||||
if (event.target != document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to open as a toplevel chrome window so that
|
||||
// DRAWWINDOW_USE_WIDGET_LAYERS is honored.
|
||||
sourceWindow = window.open("file_drawWindow_source.html", "",
|
||||
"chrome,width=200,height=100");
|
||||
sourceWindow.addEventListener("load", runTests, false);
|
||||
}
|
||||
|
||||
function runTests(event) {
|
||||
if (event.target != sourceWindow.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cxInterfaceWrap = SpecialPowers.wrap(CanvasRenderingContext2D);
|
||||
var flags = cxInterfaceWrap.DRAWWINDOW_USE_WIDGET_LAYERS |
|
||||
cxInterfaceWrap.DRAWWINDOW_DRAW_CARET |
|
||||
cxInterfaceWrap.DRAWWINDOW_DRAW_VIEW;
|
||||
runDrawWindowTests(sourceWindow, flags, true);
|
||||
|
||||
sourceWindow.close();
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||
</body>
|
||||
</html>
|
157
content/canvas/test/file_drawWindow_common.js
Normal file
157
content/canvas/test/file_drawWindow_common.js
Normal file
@ -0,0 +1,157 @@
|
||||
function runDrawWindowTests(win, drawWindowFlags, transparentBackground) {
|
||||
const CANVAS_WIDTH = 200;
|
||||
const CANVAS_HEIGHT = 100;
|
||||
|
||||
function make_canvas() {
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("height", CANVAS_HEIGHT);
|
||||
canvas.setAttribute("width", CANVAS_WIDTH);
|
||||
document.body.appendChild(canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
var testCanvas = make_canvas();
|
||||
var refCanvas = make_canvas();
|
||||
|
||||
var testCx = testCanvas.getContext("2d");
|
||||
var refCx = refCanvas.getContext("2d");
|
||||
|
||||
var testWrapCx = SpecialPowers.wrap(testCx);
|
||||
var refWrapCx = SpecialPowers.wrap(refCx);
|
||||
|
||||
function clearRef(fillStyle) {
|
||||
refCx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
refCx.fillStyle = fillStyle;
|
||||
refCx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
}
|
||||
function clearTest(fillStyle) {
|
||||
testCx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
testCx.fillStyle = fillStyle;
|
||||
testCx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
}
|
||||
function clear(fillStyle) {
|
||||
clearRef(fillStyle);
|
||||
clearTest(fillStyle);
|
||||
}
|
||||
|
||||
// Basic tests of drawing the whole document on a background
|
||||
|
||||
clear("white");
|
||||
testWrapCx.drawWindow(win, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
|
||||
"rgb(255, 255, 255)", drawWindowFlags);
|
||||
refCx.fillStyle = "fuchsia";
|
||||
refCx.fillRect(10, 10, 20, 20);
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(50, 10, 20, 20);
|
||||
refCx.fillStyle = "yellow";
|
||||
refCx.fillRect(90, 10, 20, 20);
|
||||
assertSnapshots(testCanvas, refCanvas, true /* equal */,
|
||||
"full draw of source on white background", "reference");
|
||||
|
||||
clearTest("white");
|
||||
testWrapCx.drawWindow(win, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
|
||||
"rgb(255, 255, 0)", drawWindowFlags);
|
||||
assertSnapshots(testCanvas, refCanvas,
|
||||
!transparentBackground /* not equal */,
|
||||
"full draw of source on yellow background", "reference");
|
||||
|
||||
clearRef("yellow");
|
||||
refCx.fillStyle = "fuchsia";
|
||||
refCx.fillRect(10, 10, 20, 20);
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(50, 10, 20, 20);
|
||||
refCx.fillStyle = "yellow";
|
||||
refCx.fillRect(90, 10, 20, 20);
|
||||
|
||||
assertSnapshots(testCanvas, refCanvas, transparentBackground /* equal */,
|
||||
"full draw of source on yellow background", "reference");
|
||||
|
||||
// Test drawing a region within the document.
|
||||
|
||||
clear("white");
|
||||
|
||||
testCx.translate(17, 31);
|
||||
testWrapCx.drawWindow(win, 40, 0, 40, 40,
|
||||
"white", drawWindowFlags);
|
||||
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(17 + 10, 31 + 10, 20, 20);
|
||||
|
||||
assertSnapshots(testCanvas, refCanvas, true /* equal */,
|
||||
"draw of subrect of source with matching background",
|
||||
"reference");
|
||||
|
||||
clear("blue");
|
||||
|
||||
testCx.translate(17, 31);
|
||||
testWrapCx.drawWindow(win, 40, 0, 35, 45,
|
||||
"green", drawWindowFlags);
|
||||
|
||||
if (transparentBackground) {
|
||||
refCx.fillStyle = "green";
|
||||
} else {
|
||||
refCx.fillStyle = "white";
|
||||
}
|
||||
refCx.fillRect(17, 31, 35, 45);
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(17 + 10, 31 + 10, 20, 20);
|
||||
|
||||
assertSnapshots(testCanvas, refCanvas, true /* equal */,
|
||||
"draw of subrect of source with different background",
|
||||
"reference");
|
||||
|
||||
// Test transparency of background not disturbing what is behind
|
||||
clear("blue");
|
||||
|
||||
testCx.translate(17, 31);
|
||||
testWrapCx.drawWindow(win, 40, 0, 35, 45,
|
||||
"transparent", drawWindowFlags);
|
||||
|
||||
if (!transparentBackground) {
|
||||
refCx.fillStyle = "white";
|
||||
refCx.fillRect(17, 31, 35, 45);
|
||||
}
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(17 + 10, 31 + 10, 20, 20);
|
||||
|
||||
assertSnapshots(testCanvas, refCanvas, true /* equal */,
|
||||
"draw of subrect of source with different background",
|
||||
"reference");
|
||||
|
||||
// Test that multiple drawWindow calls draw at correct positions.
|
||||
clear("blue");
|
||||
|
||||
testCx.translate(9, 3);
|
||||
// 5, 8 is 5, 2 from the corner of the fuchsia square
|
||||
testWrapCx.drawWindow(win, 5, 8, 30, 25,
|
||||
"maroon", drawWindowFlags);
|
||||
// 35, 0 is 15, 10 from the corner of the aqua square
|
||||
testWrapCx.drawWindow(win, 35, 0, 50, 40,
|
||||
"transparent", drawWindowFlags);
|
||||
testCx.translate(15, 0);
|
||||
// 85, 5 is 5, 5 from the corner of the yellow square
|
||||
testWrapCx.drawWindow(win, 85, 5, 30, 25,
|
||||
"transparent", drawWindowFlags);
|
||||
|
||||
if (transparentBackground) {
|
||||
refCx.fillStyle = "maroon";
|
||||
refCx.fillRect(9, 3, 30, 25);
|
||||
refCx.fillStyle = "fuchsia";
|
||||
refCx.fillRect(9 + 5, 3 + 2, 20, 20);
|
||||
} else {
|
||||
refCx.fillStyle = "white";
|
||||
refCx.fillRect(9, 3, 50, 40);
|
||||
}
|
||||
refCx.fillStyle = "aqua";
|
||||
refCx.fillRect(9 + 15, 3 + 10, 20, 20);
|
||||
if (!transparentBackground) {
|
||||
refCx.fillStyle = "white";
|
||||
refCx.fillRect(9 + 15, 3, 30, 25);
|
||||
}
|
||||
refCx.fillStyle = "yellow";
|
||||
refCx.fillRect(9 + 15 + 5, 3 + 0 + 5, 20, 20);
|
||||
|
||||
assertSnapshots(testCanvas, refCanvas, true /* equal */,
|
||||
"multiple drawWindow calls on top of each other",
|
||||
"reference");
|
||||
}
|
10
content/canvas/test/file_drawWindow_source.html
Normal file
10
content/canvas/test/file_drawWindow_source.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE HTML>
|
||||
<style>
|
||||
|
||||
html, body { margin: 0; padding: 0 }
|
||||
div { display: inline-block; margin: 10px; width: 20px; height: 20px }
|
||||
|
||||
</style
|
||||
><div style="background: fuchsia"></div
|
||||
><div style="background: aqua"></div
|
||||
><div style="background: yellow"></div>
|
@ -199,6 +199,9 @@ skip-if = os == "android" || appname == "b2g"
|
||||
[test_drawImageIncomplete.html]
|
||||
[test_drawImage_document_domain.html]
|
||||
[test_drawImage_edge_cases.html]
|
||||
[test_drawWindow.html]
|
||||
support-files = file_drawWindow_source.html file_drawWindow_common.js
|
||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk')
|
||||
[test_ImageData_ctor.html]
|
||||
[test_isPointInStroke.html]
|
||||
[test_mozDashOffset.html]
|
||||
|
@ -33,7 +33,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -65,7 +65,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -60,7 +60,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -44,7 +44,7 @@ function runTest() {
|
||||
else
|
||||
renderBackup(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -42,7 +42,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -41,7 +41,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -41,7 +41,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
rAF(testComplete);
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -42,7 +42,7 @@ function runTest() {
|
||||
else
|
||||
renderFailure(canvas);
|
||||
|
||||
testComplete();
|
||||
waitForComposite(testComplete);
|
||||
}
|
||||
|
||||
function testComplete() {
|
||||
|
@ -66,3 +66,18 @@ function rAF(func) {
|
||||
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame;
|
||||
raf(func);
|
||||
}
|
||||
|
||||
var MAX_WAIT_FOR_COMPOSITE_DELAY_MS = 500;
|
||||
|
||||
function waitForComposite(func) {
|
||||
var isDone = false;
|
||||
var doneFunc = function () {
|
||||
if (isDone)
|
||||
return;
|
||||
isDone = true;
|
||||
func();
|
||||
};
|
||||
|
||||
rAF(doneFunc);
|
||||
setTimeout(doneFunc, MAX_WAIT_FOR_COMPOSITE_DELAY_MS);
|
||||
}
|
||||
|
55
content/canvas/test/test_drawWindow.html
Normal file
55
content/canvas/test/test_drawWindow.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for canvas drawWindow</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<script type="application/javascript" src="file_drawWindow_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.addEventListener("load", openSourceWindow, false);
|
||||
|
||||
var sourceWindow;
|
||||
|
||||
function openSourceWindow(event) {
|
||||
if (event.target != document) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceWindow = window.open("file_drawWindow_source.html", "",
|
||||
"width=200,height=100");
|
||||
sourceWindow.addEventListener("load", runTests, false);
|
||||
}
|
||||
|
||||
function runTests(event) {
|
||||
if (event.target != sourceWindow.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the tests with the source document in an <iframe> within this
|
||||
// page, which we expect to have transparency.
|
||||
runDrawWindowTests(document.getElementById("source").contentWindow,
|
||||
0, true);
|
||||
|
||||
// Run the tests on the same source document, but in a window opened
|
||||
// by window.open. We do not expect this to have transparency...
|
||||
// except on B2G. (This is *probably* a bug in B2G.)
|
||||
var isB2G = /Mobile|Tablet/.test(navigator.userAgent) &&
|
||||
!navigator.userAgent.contains("Android");
|
||||
runDrawWindowTests(sourceWindow, 0, isB2G);
|
||||
|
||||
sourceWindow.close();
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
|
||||
<iframe id="source" src="file_drawWindow_source.html" width="200" height="100"></iframe>
|
||||
</body>
|
||||
</html>
|
@ -1252,7 +1252,7 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue)
|
||||
// Note that any script that's directly trying to access our value
|
||||
// has to be going through some scriptable object to do that and that
|
||||
// already does the relevant security checks.
|
||||
AutoSystemCaller asc;
|
||||
AutoNoJSAPI nojsapi;
|
||||
|
||||
rv = newEditor->Init(domdoc, GetRootNode(), mSelCon, editorFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -1740,8 +1740,8 @@ nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const
|
||||
// XXXbz if we could just get the textContent of our anonymous content (eg
|
||||
// if plaintext editor didn't create <br> nodes all over), we wouldn't need
|
||||
// this.
|
||||
{ /* Scope for AutoSystemCaller. */
|
||||
AutoSystemCaller asc;
|
||||
{ /* Scope for AutoNoJSAPI. */
|
||||
AutoNoJSAPI nojsapi;
|
||||
|
||||
mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
|
||||
aValue);
|
||||
@ -1820,7 +1820,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput,
|
||||
// for why this is needed. Note that we have to do this up here, because
|
||||
// otherwise SelectAll() will fail.
|
||||
{
|
||||
AutoSystemCaller asc;
|
||||
AutoNoJSAPI nojsapi;
|
||||
|
||||
nsCOMPtr<nsISelection> domSel;
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv;
|
||||
|
@ -18,8 +18,16 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gAudioStreamLog = nullptr;
|
||||
// For simple logs
|
||||
#define LOG(x) PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, x)
|
||||
#else
|
||||
#define LOG(x)
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -149,6 +157,7 @@ AudioStream::AudioStream()
|
||||
, mVolume(1.0)
|
||||
, mBytesPerFrame(0)
|
||||
, mState(INITIALIZED)
|
||||
, mNeedsStart(false)
|
||||
{
|
||||
// keep a ref in case we shut down later than nsLayoutStatics
|
||||
mLatencyLog = AsyncLatencyLogger::Get(true);
|
||||
@ -156,6 +165,7 @@ AudioStream::AudioStream()
|
||||
|
||||
AudioStream::~AudioStream()
|
||||
{
|
||||
LOG(("AudioStream: delete %p, state %d", this, mState));
|
||||
Shutdown();
|
||||
if (mDumpFile) {
|
||||
fclose(mDumpFile);
|
||||
@ -347,19 +357,19 @@ WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
|
||||
fflush(aDumpFile);
|
||||
}
|
||||
|
||||
// NOTE: this must not block a LowLatency stream for any significant amount
|
||||
// of time, or it will block the entirety of MSG
|
||||
nsresult
|
||||
AudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannel aAudioChannel,
|
||||
LatencyRequest aLatencyRequest)
|
||||
{
|
||||
cubeb* cubebContext = GetCubebContext();
|
||||
|
||||
if (!cubebContext || aNumChannels < 0 || aRate < 0) {
|
||||
if (!GetCubebContext() || aNumChannels < 0 || aRate < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_DEBUG,
|
||||
("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate));
|
||||
("%s channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this));
|
||||
mInRate = mOutRate = aRate;
|
||||
mChannels = aNumChannels;
|
||||
mOutChannels = (aNumChannels > 2) ? 2 : aNumChannels;
|
||||
@ -390,12 +400,49 @@ AudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
|
||||
mAudioClock.Init();
|
||||
|
||||
// Size mBuffer for one second of audio. This value is arbitrary, and was
|
||||
// selected based on the observed behaviour of the existing AudioStream
|
||||
// implementations.
|
||||
uint32_t bufferLimit = FramesToBytes(aRate);
|
||||
NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames");
|
||||
mBuffer.SetCapacity(bufferLimit);
|
||||
|
||||
if (aLatencyRequest == LowLatency) {
|
||||
// Don't block this thread to initialize a cubeb stream.
|
||||
// When this is done, it will start callbacks from Cubeb. Those will
|
||||
// cause us to move from INITIALIZED to RUNNING. Until then, we
|
||||
// can't access any cubeb functions.
|
||||
AudioInitTask *init = new AudioInitTask(this, aLatencyRequest, params);
|
||||
init->Dispatch();
|
||||
return NS_OK;
|
||||
}
|
||||
// High latency - open synchronously
|
||||
nsresult rv = OpenCubeb(params, aLatencyRequest);
|
||||
// See if we need to start() the stream, since we must do that from this
|
||||
// thread for now (cubeb API issue)
|
||||
CheckForStart();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// This code used to live inside AudioStream::Init(), but on Mac (others?)
|
||||
// it has been known to take 300-800 (or even 8500) ms to execute(!)
|
||||
nsresult
|
||||
AudioStream::OpenCubeb(cubeb_stream_params &aParams,
|
||||
LatencyRequest aLatencyRequest)
|
||||
{
|
||||
cubeb* cubebContext = GetCubebContext();
|
||||
if (!cubebContext) {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mState = AudioStream::ERRORED;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// If the latency pref is set, use it. Otherwise, if this stream is intended
|
||||
// for low latency playback, try to get the lowest latency possible.
|
||||
// Otherwise, for normal streams, use 100ms.
|
||||
uint32_t latency;
|
||||
if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) {
|
||||
if (cubeb_get_min_latency(cubebContext, params, &latency) != CUBEB_OK) {
|
||||
if (cubeb_get_min_latency(cubebContext, aParams, &latency) != CUBEB_OK) {
|
||||
latency = GetCubebLatency();
|
||||
}
|
||||
} else {
|
||||
@ -404,42 +451,67 @@ AudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
|
||||
{
|
||||
cubeb_stream* stream;
|
||||
if (cubeb_stream_init(cubebContext, &stream, "AudioStream", params,
|
||||
if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams,
|
||||
latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mCubebStream.own(stream);
|
||||
// Make sure we weren't shut down while in flight!
|
||||
if (mState == SHUTDOWN) {
|
||||
mCubebStream.reset();
|
||||
LOG(("AudioStream::OpenCubeb() %p Shutdown while opening cubeb", this));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// We can't cubeb_stream_start() the thread from a transient thread due to
|
||||
// cubeb API requirements (init can be called from another thread, but
|
||||
// not start/stop/destroy/etc)
|
||||
} else {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mState = ERRORED;
|
||||
LOG(("AudioStream::OpenCubeb() %p failed to init cubeb", this));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mCubebStream) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Size mBuffer for one second of audio. This value is arbitrary, and was
|
||||
// selected based on the observed behaviour of the existing AudioStream
|
||||
// implementations.
|
||||
uint32_t bufferLimit = FramesToBytes(aRate);
|
||||
NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames");
|
||||
mBuffer.SetCapacity(bufferLimit);
|
||||
|
||||
// Start the stream right away when low latency has been requested. This means
|
||||
// that the DataCallback will feed silence to cubeb, until the first frames
|
||||
// are writtent to this AudioStream.
|
||||
if (mLatencyRequest == LowLatency) {
|
||||
Start();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
AudioStream::Shutdown()
|
||||
AudioStream::CheckForStart()
|
||||
{
|
||||
if (mState == STARTED) {
|
||||
Pause();
|
||||
if (mState == INITIALIZED) {
|
||||
// Start the stream right away when low latency has been requested. This means
|
||||
// that the DataCallback will feed silence to cubeb, until the first frames
|
||||
// are written to this AudioStream. Also start if a start has been queued.
|
||||
if (mLatencyRequest == LowLatency || mNeedsStart) {
|
||||
StartUnlocked(); // mState = STARTED or ERRORED
|
||||
mNeedsStart = false;
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
|
||||
("Started waiting %s-latency stream",
|
||||
mLatencyRequest == LowLatency ? "low" : "high"));
|
||||
} else {
|
||||
// high latency, not full - OR Pause() was called before we got here
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_DEBUG,
|
||||
("Not starting waiting %s-latency stream",
|
||||
mLatencyRequest == LowLatency ? "low" : "high"));
|
||||
}
|
||||
}
|
||||
if (mCubebStream) {
|
||||
mCubebStream.reset();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
AudioInitTask::Run()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
mThread->Shutdown(); // can't Shutdown from the thread itself, darn
|
||||
mThread = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = mAudioStream->OpenCubeb(mParams, mLatencyRequest);
|
||||
|
||||
// and now kill this thread
|
||||
NS_DispatchToMainThread(this);
|
||||
return rv;
|
||||
}
|
||||
|
||||
// aTime is the time in ms the samples were inserted into MediaStreamGraph
|
||||
@ -447,12 +519,15 @@ nsresult
|
||||
AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (!mCubebStream || mState == ERRORED) {
|
||||
if (mState == ERRORED) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
NS_ASSERTION(mState == INITIALIZED || mState == STARTED,
|
||||
NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING,
|
||||
"Stream write in unexpected state.");
|
||||
|
||||
// See if we need to start() the stream, since we must do that from this thread
|
||||
CheckForStart();
|
||||
|
||||
// Downmix to Stereo.
|
||||
if (mChannels > 2 && mChannels <= 8) {
|
||||
DownmixAudioToStereo(const_cast<AudioDataValue*> (aBuf), mChannels, aFrames);
|
||||
@ -490,15 +565,33 @@ AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTim
|
||||
bytesToCopy -= available;
|
||||
|
||||
if (bytesToCopy > 0) {
|
||||
// If we are not playing, but our buffer is full, start playing to make
|
||||
// room for soon-to-be-decoded data.
|
||||
if (mState != STARTED) {
|
||||
StartUnlocked();
|
||||
if (mState != STARTED) {
|
||||
return NS_ERROR_FAILURE;
|
||||
// Careful - the CubebInit thread may not have gotten to STARTED yet
|
||||
if ((mState == INITIALIZED || mState == STARTED) && mLatencyRequest == LowLatency) {
|
||||
// don't ever block MediaStreamGraph low-latency streams
|
||||
uint32_t remains = 0; // we presume the buffer is full
|
||||
if (mBuffer.Length() > bytesToCopy) {
|
||||
remains = mBuffer.Length() - bytesToCopy; // Free up just enough space
|
||||
}
|
||||
// account for dropping samples
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p dropping %u bytes (%u frames)in Write()",
|
||||
this, mBuffer.Length() - remains, BytesToFrames(mBuffer.Length() - remains)));
|
||||
mReadPoint += BytesToFrames(mBuffer.Length() - remains);
|
||||
mBuffer.ContractTo(remains);
|
||||
} else { // RUNNING or high latency
|
||||
// If we are not playing, but our buffer is full, start playing to make
|
||||
// room for soon-to-be-decoded data.
|
||||
if (mState != STARTED && mState != RUNNING) {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Starting stream %p in Write (%u waiting)",
|
||||
this, bytesToCopy));
|
||||
StartUnlocked();
|
||||
if (mState == ERRORED) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p waiting in Write() (%u waiting)",
|
||||
this, bytesToCopy));
|
||||
mon.Wait();
|
||||
}
|
||||
mon.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@ -526,8 +619,9 @@ void
|
||||
AudioStream::Drain()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mState != STARTED) {
|
||||
NS_ASSERTION(mBuffer.Available() == 0, "Draining with unplayed audio");
|
||||
LOG(("AudioStream::Drain() for %p, state %d, avail %u", this, mState, mBuffer.Available()));
|
||||
if (mState != STARTED && mState != RUNNING) {
|
||||
NS_ASSERTION(mState == ERRORED || mBuffer.Available() == 0, "Draining without full buffer of unplayed audio");
|
||||
return;
|
||||
}
|
||||
mState = DRAINING;
|
||||
@ -547,18 +641,15 @@ void
|
||||
AudioStream::StartUnlocked()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (!mCubebStream || mState != INITIALIZED) {
|
||||
if (!mCubebStream) {
|
||||
mNeedsStart = true;
|
||||
return;
|
||||
}
|
||||
if (mState != STARTED) {
|
||||
int r;
|
||||
{
|
||||
MonitorAutoUnlock mon(mMonitor);
|
||||
r = cubeb_stream_start(mCubebStream);
|
||||
}
|
||||
if (mState != ERRORED) {
|
||||
mState = r == CUBEB_OK ? STARTED : ERRORED;
|
||||
}
|
||||
MonitorAutoUnlock mon(mMonitor);
|
||||
if (mState == INITIALIZED) {
|
||||
int r = cubeb_stream_start(mCubebStream);
|
||||
mState = r == CUBEB_OK ? STARTED : ERRORED;
|
||||
LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,7 +657,9 @@ void
|
||||
AudioStream::Pause()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (!mCubebStream || mState != STARTED) {
|
||||
if (!mCubebStream || (mState != STARTED && mState != RUNNING)) {
|
||||
mNeedsStart = false;
|
||||
mState = STOPPED; // which also tells async OpenCubeb not to start, just init
|
||||
return;
|
||||
}
|
||||
|
||||
@ -598,6 +691,26 @@ AudioStream::Resume()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioStream::Shutdown()
|
||||
{
|
||||
LOG(("AudioStream: Shutdown %p, state %d", this, mState));
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mState == STARTED || mState == RUNNING) {
|
||||
MonitorAutoUnlock mon(mMonitor);
|
||||
Pause();
|
||||
}
|
||||
MOZ_ASSERT(mState != STARTED && mState != RUNNING); // paranoia
|
||||
mState = SHUTDOWN;
|
||||
}
|
||||
// Must not try to shut down cubeb from within the lock! wasapi may still
|
||||
// call our callback after Pause()/stop()!?! Bug 996162
|
||||
if (mCubebStream) {
|
||||
mCubebStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t
|
||||
AudioStream::GetPosition()
|
||||
{
|
||||
@ -792,6 +905,42 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
|
||||
uint32_t servicedFrames = 0;
|
||||
int64_t insertTime;
|
||||
|
||||
// NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
|
||||
// Bug 996162
|
||||
|
||||
// callback tells us cubeb succeeded initializing
|
||||
if (mState == STARTED) {
|
||||
// For low-latency streams, we want to minimize any built-up data when
|
||||
// we start getting callbacks.
|
||||
// Simple version - contract on first callback only.
|
||||
if (mLatencyRequest == LowLatency) {
|
||||
#ifdef PR_LOGGING
|
||||
uint32_t old_len = mBuffer.Length();
|
||||
#endif
|
||||
available = mBuffer.ContractTo(FramesToBytes(aFrames));
|
||||
#ifdef PR_LOGGING
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
if (!mStartTime.IsNull()) {
|
||||
int64_t timeMs = (now - mStartTime).ToMilliseconds();
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
|
||||
("Stream took %lldms to start after first Write() @ %u", timeMs, mOutRate));
|
||||
} else {
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
|
||||
("Stream started before Write() @ %u", mOutRate));
|
||||
}
|
||||
|
||||
if (old_len != available) {
|
||||
// Note that we may have dropped samples in Write() as well!
|
||||
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
|
||||
("AudioStream %p dropped %u + %u initial frames @ %u", this,
|
||||
mReadPoint, BytesToFrames(old_len - available), mOutRate));
|
||||
mReadPoint += BytesToFrames(old_len - available);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
mState = RUNNING;
|
||||
}
|
||||
|
||||
if (available) {
|
||||
// When we are playing a low latency stream, and it is the first time we are
|
||||
// getting data from the buffer, we prefer to add the silence for an
|
||||
@ -834,6 +983,7 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
|
||||
WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
|
||||
// Don't log if we're not interested or if the stream is inactive
|
||||
if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) &&
|
||||
mState != SHUTDOWN &&
|
||||
insertTime != INT64_MAX && servicedFrames > underrunFrames) {
|
||||
uint32_t latency = UINT32_MAX;
|
||||
if (cubeb_stream_get_latency(mCubebStream, &latency)) {
|
||||
@ -858,6 +1008,7 @@ AudioStream::StateCallback(cubeb_state aState)
|
||||
if (aState == CUBEB_STATE_DRAINED) {
|
||||
mState = DRAINED;
|
||||
} else if (aState == CUBEB_STATE_ERROR) {
|
||||
LOG(("AudioStream::StateCallback() state %d cubeb error", mState));
|
||||
mState = ERRORED;
|
||||
}
|
||||
mon.NotifyAll();
|
||||
|
@ -10,9 +10,11 @@
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsAutoRef.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "Latency.h"
|
||||
#include "mozilla/dom/AudioChannelBinding.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
@ -154,6 +156,19 @@ public:
|
||||
mStart %= mCapacity;
|
||||
}
|
||||
|
||||
// Throw away all but aSize bytes from the buffer. Returns new size, which
|
||||
// may be less than aSize
|
||||
uint32_t ContractTo(uint32_t aSize) {
|
||||
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
|
||||
if (aSize >= mCount) {
|
||||
return mCount;
|
||||
}
|
||||
mStart += (mCount - aSize);
|
||||
mCount = aSize;
|
||||
mStart %= mCapacity;
|
||||
return mCount;
|
||||
}
|
||||
|
||||
private:
|
||||
nsAutoArrayPtr<uint8_t> mBuffer;
|
||||
uint32_t mCapacity;
|
||||
@ -161,6 +176,8 @@ private:
|
||||
uint32_t mCount;
|
||||
};
|
||||
|
||||
class AudioInitTask;
|
||||
|
||||
// Access to a single instance of this class must be synchronized by
|
||||
// callers, or made from a single thread. One exception is that access to
|
||||
// GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels}
|
||||
@ -186,8 +203,9 @@ public:
|
||||
// Get the aformentionned sample rate. Does not lock.
|
||||
static int PreferredSampleRate();
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioStream)
|
||||
AudioStream();
|
||||
~AudioStream();
|
||||
virtual ~AudioStream();
|
||||
|
||||
enum LatencyRequest {
|
||||
HighLatency,
|
||||
@ -263,6 +281,14 @@ public:
|
||||
nsresult SetPreservesPitch(bool aPreservesPitch);
|
||||
|
||||
private:
|
||||
friend class AudioInitTask;
|
||||
|
||||
// So we can call it asynchronously from AudioInitTask
|
||||
nsresult OpenCubeb(cubeb_stream_params &aParams,
|
||||
LatencyRequest aLatencyRequest);
|
||||
|
||||
void CheckForStart();
|
||||
|
||||
static void PrefChanged(const char* aPref, void* aClosure);
|
||||
static double GetVolumeScale();
|
||||
static cubeb* GetCubebContext();
|
||||
@ -366,17 +392,20 @@ private:
|
||||
|
||||
enum StreamState {
|
||||
INITIALIZED, // Initialized, playback has not begun.
|
||||
STARTED, // Started by a call to Write() (iff INITIALIZED) or Resume().
|
||||
STARTED, // cubeb started, but callbacks haven't started
|
||||
RUNNING, // DataCallbacks have started after STARTED, or after Resume().
|
||||
STOPPED, // Stopped by a call to Pause().
|
||||
DRAINING, // Drain requested. DataCallback will indicate end of stream
|
||||
// once the remaining contents of mBuffer are requested by
|
||||
// cubeb, after which StateCallback will indicate drain
|
||||
// completion.
|
||||
DRAINED, // StateCallback has indicated that the drain is complete.
|
||||
ERRORED // Stream disabled due to an internal error.
|
||||
ERRORED, // Stream disabled due to an internal error.
|
||||
SHUTDOWN // Shutdown has been called
|
||||
};
|
||||
|
||||
StreamState mState;
|
||||
bool mNeedsStart; // needed in case Start() is called before cubeb is open
|
||||
|
||||
// This mutex protects the static members below.
|
||||
static StaticMutex sMutex;
|
||||
@ -391,6 +420,35 @@ private:
|
||||
static bool sCubebLatencyPrefSet;
|
||||
};
|
||||
|
||||
class AudioInitTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
AudioInitTask(AudioStream *aStream,
|
||||
AudioStream::LatencyRequest aLatencyRequest,
|
||||
const cubeb_stream_params &aParams)
|
||||
: mAudioStream(aStream)
|
||||
, mLatencyRequest(aLatencyRequest)
|
||||
, mParams(aParams)
|
||||
{}
|
||||
|
||||
nsresult Dispatch()
|
||||
{
|
||||
return NS_NewNamedThread("CubebInit", getter_AddRefs(mThread), this);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~AudioInitTask() {};
|
||||
|
||||
private:
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL;
|
||||
|
||||
RefPtr<AudioStream> mAudioStream;
|
||||
AudioStream::LatencyRequest mLatencyRequest;
|
||||
cubeb_stream_params mParams;
|
||||
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
@ -783,7 +783,7 @@ void MediaDecoderStateMachine::AudioLoop()
|
||||
// AudioStream initialization can block for extended periods in unusual
|
||||
// circumstances, so we take care to drop the decoder monitor while
|
||||
// initializing.
|
||||
nsAutoPtr<AudioStream> audioStream(new AudioStream());
|
||||
RefPtr<AudioStream> audioStream(new AudioStream());
|
||||
audioStream->Init(channels, rate, audioChannel, AudioStream::HighLatency);
|
||||
audioStream->SetVolume(volume);
|
||||
if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
|
||||
@ -799,7 +799,7 @@ void MediaDecoderStateMachine::AudioLoop()
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mAudioStream = audioStream;
|
||||
mAudioStream = audioStream.forget();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1556,6 +1556,7 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
|
||||
(!needToDecodeAudio && !needToDecodeVideo));
|
||||
|
||||
bool needIdle = !mDecoder->IsLogicallyPlaying() &&
|
||||
mState != DECODER_STATE_SEEKING &&
|
||||
!needToDecodeAudio &&
|
||||
!needToDecodeVideo &&
|
||||
!IsPlaying();
|
||||
|
@ -731,7 +731,7 @@ private:
|
||||
// This is created and destroyed on the audio thread, while holding the
|
||||
// decoder monitor, so if this is used off the audio thread, you must
|
||||
// first acquire the decoder monitor and check that it is non-null.
|
||||
nsAutoPtr<AudioStream> mAudioStream;
|
||||
RefPtr<AudioStream> mAudioStream;
|
||||
|
||||
// The reader, don't call its methods with the decoder monitor held.
|
||||
// This is created in the play state machine's constructor, and destroyed
|
||||
|
@ -831,9 +831,7 @@ MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTim
|
||||
continue;
|
||||
}
|
||||
|
||||
// XXX allocating a AudioStream could be slow so we're going to have to do
|
||||
// something here ... preallocation, async allocation, multiplexing onto a single
|
||||
// stream ...
|
||||
// Allocating a AudioStream would be slow, so we finish the Init async
|
||||
MediaStream::AudioOutputStream* audioOutputStream =
|
||||
aStream->mAudioOutputStreams.AppendElement();
|
||||
audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
|
||||
@ -842,6 +840,7 @@ MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTim
|
||||
audioOutputStream->mStream = new AudioStream();
|
||||
// XXX for now, allocate stereo output. But we need to fix this to
|
||||
// match the system's ideal channel configuration.
|
||||
// NOTE: we presume this is either fast or async-under-the-covers
|
||||
audioOutputStream->mStream->Init(2, IdealAudioRate(),
|
||||
AudioChannel::Normal,
|
||||
AudioStream::LowLatency);
|
||||
|
@ -575,7 +575,7 @@ protected:
|
||||
MediaTime mBlockedAudioTime;
|
||||
// Last tick written to the audio output.
|
||||
TrackTicks mLastTickWritten;
|
||||
nsAutoPtr<AudioStream> mStream;
|
||||
RefPtr<AudioStream> mStream;
|
||||
TrackID mTrackID;
|
||||
};
|
||||
nsTArray<AudioOutputStream> mAudioOutputStreams;
|
||||
|
@ -40,13 +40,16 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
#define DECODER_LOG(type, msg)
|
||||
#endif
|
||||
|
||||
MediaOmxReader::MediaOmxReader(AbstractMediaDecoder *aDecoder) :
|
||||
MediaDecoderReader(aDecoder),
|
||||
mHasVideo(false),
|
||||
mHasAudio(false),
|
||||
mVideoSeekTimeUs(-1),
|
||||
mAudioSeekTimeUs(-1),
|
||||
mSkipCount(0)
|
||||
MediaOmxReader::MediaOmxReader(AbstractMediaDecoder *aDecoder)
|
||||
: MediaDecoderReader(aDecoder)
|
||||
, mHasVideo(false)
|
||||
, mHasAudio(false)
|
||||
, mVideoSeekTimeUs(-1)
|
||||
, mAudioSeekTimeUs(-1)
|
||||
, mSkipCount(0)
|
||||
#ifdef DEBUG
|
||||
, mIsActive(true)
|
||||
#endif
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
if (!gMediaDecoderLog) {
|
||||
@ -132,6 +135,7 @@ nsresult MediaOmxReader::ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
MOZ_ASSERT(mIsActive);
|
||||
|
||||
*aTags = nullptr;
|
||||
|
||||
@ -207,6 +211,8 @@ nsresult MediaOmxReader::ReadMetadata(MediaInfo* aInfo,
|
||||
bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
int64_t aTimeThreshold)
|
||||
{
|
||||
MOZ_ASSERT(mIsActive);
|
||||
|
||||
// Record number of frames decoded and parsed. Automatically update the
|
||||
// stats counters using the AutoNotifyDecoded stack-based class.
|
||||
uint32_t parsed = 0, decoded = 0;
|
||||
@ -335,6 +341,7 @@ void MediaOmxReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, in
|
||||
bool MediaOmxReader::DecodeAudioData()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
MOZ_ASSERT(mIsActive);
|
||||
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
@ -368,6 +375,7 @@ bool MediaOmxReader::DecodeAudioData()
|
||||
nsresult MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
MOZ_ASSERT(mIsActive);
|
||||
|
||||
ResetDecode();
|
||||
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
|
||||
@ -402,6 +410,9 @@ static uint64_t BytesToTime(int64_t offset, uint64_t length, uint64_t durationUs
|
||||
}
|
||||
|
||||
void MediaOmxReader::SetIdle() {
|
||||
#ifdef DEBUG
|
||||
mIsActive = false;
|
||||
#endif
|
||||
if (!mOmxDecoder.get()) {
|
||||
return;
|
||||
}
|
||||
@ -409,6 +420,9 @@ void MediaOmxReader::SetIdle() {
|
||||
}
|
||||
|
||||
void MediaOmxReader::SetActive() {
|
||||
#ifdef DEBUG
|
||||
mIsActive = true;
|
||||
#endif
|
||||
if (!mOmxDecoder.get()) {
|
||||
return;
|
||||
}
|
||||
|
@ -100,6 +100,11 @@ public:
|
||||
void CheckAudioOffload();
|
||||
#endif
|
||||
|
||||
private:
|
||||
// This flag is true when SetActive() has been called without a matching
|
||||
// SetIdle(). This is used to sanity check the SetIdle/SetActive calls, to
|
||||
// ensure SetActive has been called before a decode call.
|
||||
DebugOnly<bool> mIsActive;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -2397,6 +2397,7 @@ nsXULPrototypeScript::Serialize(nsIObjectOutputStream* aStream,
|
||||
nsXULPrototypeDocument* aProtoDoc,
|
||||
const nsCOMArray<nsINodeInfo> *aNodeInfos)
|
||||
{
|
||||
NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);
|
||||
AutoSafeJSContext cx;
|
||||
JS::Rooted<JSObject*> global(cx, aProtoDoc->GetCompilationGlobal());
|
||||
NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
|
||||
|
@ -23,7 +23,7 @@ namespace dom {
|
||||
class ScriptSettingsStack;
|
||||
static mozilla::ThreadLocal<ScriptSettingsStack*> sScriptSettingsTLS;
|
||||
|
||||
ScriptSettingsStackEntry ScriptSettingsStackEntry::SystemSingleton;
|
||||
ScriptSettingsStackEntry ScriptSettingsStackEntry::NoJSAPISingleton;
|
||||
|
||||
class ScriptSettingsStack {
|
||||
public:
|
||||
@ -34,13 +34,13 @@ public:
|
||||
|
||||
void Push(ScriptSettingsStackEntry* aSettings) {
|
||||
// The bottom-most entry must always be a candidate entry point.
|
||||
MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->IsSystemSingleton(),
|
||||
MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->NoJSAPI(),
|
||||
aSettings->mIsCandidateEntryPoint);
|
||||
mStack.AppendElement(aSettings);
|
||||
}
|
||||
|
||||
void PushSystem() {
|
||||
mStack.AppendElement(&ScriptSettingsStackEntry::SystemSingleton);
|
||||
void PushNoJSAPI() {
|
||||
mStack.AppendElement(&ScriptSettingsStackEntry::NoJSAPISingleton);
|
||||
}
|
||||
|
||||
void Pop() {
|
||||
@ -158,9 +158,9 @@ GetWebIDLCallerPrincipal()
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ScriptSettingsStackEntry *entry = ScriptSettingsStack::Ref().EntryPoint();
|
||||
|
||||
// If we have an entry point that is not the system singleton, we know it
|
||||
// If we have an entry point that is not the NoJSAPI singleton, we know it
|
||||
// must be an AutoEntryScript.
|
||||
if (!entry || entry->IsSystemSingleton()) {
|
||||
if (!entry || entry->NoJSAPI()) {
|
||||
return nullptr;
|
||||
}
|
||||
AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
|
||||
@ -184,7 +184,7 @@ GetWebIDLCallerPrincipal()
|
||||
// that we should return a non-null WebIDL Caller.
|
||||
//
|
||||
// Once we fix bug 951991, this can all be simplified.
|
||||
if (!aes->mCxPusher.ref().IsStackTop()) {
|
||||
if (!aes->CxPusherIsStackTop()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -206,28 +206,48 @@ FindJSContext(nsIGlobalObject* aGlobalObject)
|
||||
return cx;
|
||||
}
|
||||
|
||||
AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
|
||||
bool aIsMainThread,
|
||||
JSContext* aCx)
|
||||
: ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
|
||||
, mStack(ScriptSettingsStack::Ref())
|
||||
, mCx(aCx)
|
||||
AutoJSAPI::AutoJSAPI()
|
||||
: mCx(nsContentUtils::GetDefaultJSContextForThread())
|
||||
{
|
||||
MOZ_ASSERT(aGlobalObject);
|
||||
MOZ_ASSERT_IF(!mCx, aIsMainThread); // cx is mandatory off-main-thread.
|
||||
MOZ_ASSERT_IF(mCx && aIsMainThread, mCx == FindJSContext(aGlobalObject));
|
||||
if (!mCx) {
|
||||
// If the caller didn't provide a cx, hunt one down. This isn't exactly
|
||||
// fast, but the callers that care about performance can pass an explicit
|
||||
// cx for now. Eventually, the whole cx pushing thing will go away
|
||||
// entirely.
|
||||
mCx = FindJSContext(aGlobalObject);
|
||||
MOZ_ASSERT(mCx);
|
||||
if (NS_IsMainThread()) {
|
||||
mCxPusher.construct(mCx);
|
||||
}
|
||||
|
||||
// Leave the cx in a null compartment.
|
||||
mNullAc.construct(mCx);
|
||||
}
|
||||
|
||||
AutoJSAPI::AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAc)
|
||||
: mCx(aCx)
|
||||
{
|
||||
MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread());
|
||||
if (aIsMainThread) {
|
||||
mCxPusher.construct(mCx);
|
||||
}
|
||||
mAc.construct(mCx, aGlobalObject->GetGlobalJSObject());
|
||||
|
||||
// In general we want to leave the cx in a null compartment, but we let
|
||||
// subclasses skip this if they plan to immediately enter a compartment.
|
||||
if (!aSkipNullAc) {
|
||||
mNullAc.construct(mCx);
|
||||
}
|
||||
}
|
||||
|
||||
AutoJSAPIWithErrorsReportedToWindow::AutoJSAPIWithErrorsReportedToWindow(nsIScriptContext* aScx)
|
||||
: AutoJSAPI(aScx->GetNativeContext(), /* aIsMainThread = */ true)
|
||||
{
|
||||
}
|
||||
|
||||
AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
|
||||
bool aIsMainThread,
|
||||
JSContext* aCx)
|
||||
: AutoJSAPI(aCx ? aCx : FindJSContext(aGlobalObject), aIsMainThread, /* aSkipNullAc = */ true)
|
||||
, ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
|
||||
, mAc(cx(), aGlobalObject->GetGlobalJSObject())
|
||||
, mStack(ScriptSettingsStack::Ref())
|
||||
{
|
||||
MOZ_ASSERT(aGlobalObject);
|
||||
MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
|
||||
MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
|
||||
mStack.Push(this);
|
||||
}
|
||||
|
||||
@ -251,17 +271,19 @@ AutoIncumbentScript::~AutoIncumbentScript()
|
||||
mStack.Pop();
|
||||
}
|
||||
|
||||
AutoSystemCaller::AutoSystemCaller(bool aIsMainThread)
|
||||
AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread)
|
||||
: mStack(ScriptSettingsStack::Ref())
|
||||
{
|
||||
MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(),
|
||||
!JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread()));
|
||||
if (aIsMainThread) {
|
||||
mCxPusher.construct(static_cast<JSContext*>(nullptr),
|
||||
/* aAllowNull = */ true);
|
||||
}
|
||||
mStack.PushSystem();
|
||||
mStack.PushNoJSAPI();
|
||||
}
|
||||
|
||||
AutoSystemCaller::~AutoSystemCaller()
|
||||
AutoNoJSAPI::~AutoNoJSAPI()
|
||||
{
|
||||
mStack.Pop();
|
||||
}
|
||||
|
@ -86,8 +86,8 @@ struct ScriptSettingsStackEntry {
|
||||
MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
|
||||
}
|
||||
|
||||
bool IsSystemSingleton() { return this == &SystemSingleton; }
|
||||
static ScriptSettingsStackEntry SystemSingleton;
|
||||
bool NoJSAPI() { return this == &NoJSAPISingleton; }
|
||||
static ScriptSettingsStackEntry NoJSAPISingleton;
|
||||
|
||||
private:
|
||||
ScriptSettingsStackEntry() : mGlobalObject(nullptr)
|
||||
@ -95,10 +95,76 @@ private:
|
||||
{}
|
||||
};
|
||||
|
||||
/*
|
||||
* For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
|
||||
* must be on the stack.
|
||||
*
|
||||
* This base class should be instantiated as-is when the caller wants to use
|
||||
* JSAPI but doesn't expect to run script. Its current duties are as-follows:
|
||||
*
|
||||
* * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
|
||||
* the JSContext stack.
|
||||
* * Entering a null compartment, so that the consumer is forced to select a
|
||||
* compartment to enter before manipulating objects.
|
||||
*
|
||||
* Additionally, the following duties are planned, but not yet implemented:
|
||||
*
|
||||
* * De-poisoning the JSRuntime to allow manipulation of JSAPI. We can't
|
||||
* actually implement this poisoning until all the JSContext pushing in the
|
||||
* system goes through AutoJSAPI (see bug 951991). For now, this de-poisoning
|
||||
* effectively corresponds to having a non-null cx on the stack.
|
||||
* * Reporting any exceptions left on the JSRuntime, unless the caller steals
|
||||
* or silences them.
|
||||
* * Entering a JSAutoRequest. At present, this is handled by the cx pushing
|
||||
* on the main thread, and by other code on workers. Depending on the order
|
||||
* in which various cleanup lands, this may never be necessary, because
|
||||
* JSAutoRequests may go away.
|
||||
*
|
||||
* In situations where the consumer expects to run script, AutoEntryScript
|
||||
* should be used, which does additional manipulation of the script settings
|
||||
* stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
|
||||
* any attempt to run script without an AutoEntryScript on the stack will
|
||||
* fail. This prevents system code from accidentally triggering script
|
||||
* execution at inopportune moments via surreptitious getters and proxies.
|
||||
*/
|
||||
class AutoJSAPI {
|
||||
public:
|
||||
// Public constructor for use when the base class is constructed as-is. It
|
||||
// uses the SafeJSContext (or worker equivalent), and enters a null
|
||||
// compartment.
|
||||
AutoJSAPI();
|
||||
JSContext* cx() const { return mCx; }
|
||||
|
||||
bool CxPusherIsStackTop() { return mCxPusher.ref().IsStackTop(); }
|
||||
|
||||
protected:
|
||||
// Protected constructor, allowing subclasses to specify a particular cx to
|
||||
// be used.
|
||||
AutoJSAPI(JSContext *aCx, bool aIsMainThread, bool aSkipNullAC = false);
|
||||
|
||||
private:
|
||||
mozilla::Maybe<AutoCxPusher> mCxPusher;
|
||||
mozilla::Maybe<JSAutoNullCompartment> mNullAc;
|
||||
JSContext *mCx;
|
||||
};
|
||||
|
||||
// Note - the ideal way to implement this is with an accessor on AutoJSAPI
|
||||
// that lets us select the error reporting target. But at present,
|
||||
// implementing it that way would require us to destroy and reconstruct
|
||||
// mCxPusher, which is pretty wasteful. So we do this for now, since it should
|
||||
// be pretty easy to switch things over later.
|
||||
//
|
||||
// This should only be used on the main thread.
|
||||
class AutoJSAPIWithErrorsReportedToWindow : public AutoJSAPI {
|
||||
public:
|
||||
AutoJSAPIWithErrorsReportedToWindow(nsIScriptContext* aScx);
|
||||
};
|
||||
|
||||
/*
|
||||
* A class that represents a new script entry point.
|
||||
*/
|
||||
class AutoEntryScript : protected ScriptSettingsStackEntry {
|
||||
class AutoEntryScript : public AutoJSAPI,
|
||||
protected ScriptSettingsStackEntry {
|
||||
public:
|
||||
AutoEntryScript(nsIGlobalObject* aGlobalObject,
|
||||
bool aIsMainThread = NS_IsMainThread(),
|
||||
@ -110,15 +176,10 @@ public:
|
||||
mWebIDLCallerPrincipal = aPrincipal;
|
||||
}
|
||||
|
||||
JSContext* cx() const { return mCx; }
|
||||
|
||||
private:
|
||||
JSAutoCompartment mAc;
|
||||
dom::ScriptSettingsStack& mStack;
|
||||
nsCOMPtr<nsIPrincipal> mWebIDLCallerPrincipal;
|
||||
JSContext *mCx;
|
||||
mozilla::Maybe<AutoCxPusher> mCxPusher;
|
||||
mozilla::Maybe<JSAutoCompartment> mAc; // This can de-Maybe-fy when mCxPusher
|
||||
// goes away.
|
||||
friend nsIPrincipal* GetWebIDLCallerPrincipal();
|
||||
};
|
||||
|
||||
@ -135,14 +196,17 @@ private:
|
||||
};
|
||||
|
||||
/*
|
||||
* A class used for C++ to indicate that existing entry and incumbent scripts
|
||||
* should not apply to anything in scope, and that callees should act as if
|
||||
* they were invoked "from C++".
|
||||
* A class to put the JS engine in an unusable state. The subject principal
|
||||
* will become System, the information on the script settings stack is
|
||||
* rendered inaccessible, and JSAPI may not be manipulated until the class is
|
||||
* either popped or an AutoJSAPI instance is subsequently pushed.
|
||||
*
|
||||
* This class may not be instantiated if an exception is pending.
|
||||
*/
|
||||
class AutoSystemCaller {
|
||||
class AutoNoJSAPI {
|
||||
public:
|
||||
AutoSystemCaller(bool aIsMainThread = NS_IsMainThread());
|
||||
~AutoSystemCaller();
|
||||
AutoNoJSAPI(bool aIsMainThread = NS_IsMainThread());
|
||||
~AutoNoJSAPI();
|
||||
private:
|
||||
dom::ScriptSettingsStack& mStack;
|
||||
mozilla::Maybe<AutoCxPusher> mCxPusher;
|
||||
|
@ -11472,9 +11472,9 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
|
||||
// that we don't force a system caller here, because that screws it up
|
||||
// when it tries to compute the caller principal to associate with dialog
|
||||
// arguments. That whole setup just really needs to be rewritten. :-(
|
||||
Maybe<AutoSystemCaller> asc;
|
||||
Maybe<AutoNoJSAPI> nojsapi;
|
||||
if (!aContentModal) {
|
||||
asc.construct();
|
||||
nojsapi.construct();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2037,7 +2037,7 @@ ConstructJSImplementation(JSContext* aCx, const char* aContractId,
|
||||
// initializing the object, so exceptions from that will get reported
|
||||
// properly, since those are never exceptions that a spec wants to be thrown.
|
||||
{
|
||||
AutoSystemCaller asc;
|
||||
AutoNoJSAPI nojsapi;
|
||||
|
||||
// Get the XPCOM component containing the JS implementation.
|
||||
nsCOMPtr<nsISupports> implISupports = do_CreateInstance(aContractId);
|
||||
|
@ -791,8 +791,10 @@ EventListenerManager::CompileEventHandlerInternal(Listener* aListener,
|
||||
nsIScriptContext* context = global->GetScriptContext();
|
||||
NS_ENSURE_STATE(context);
|
||||
|
||||
// Push a context to make sure exceptions are reported in the right place.
|
||||
AutoPushJSContextForErrorReporting cx(context->GetNativeContext());
|
||||
// Activate JSAPI, and make sure that exceptions are reported on the right
|
||||
// Window.
|
||||
AutoJSAPIWithErrorsReportedToWindow jsapi(context);
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
nsCOMPtr<nsIAtom> typeAtom = aListener->mTypeAtom;
|
||||
nsIAtom* attrName = typeAtom;
|
||||
|
@ -15,6 +15,8 @@ KeyboardEvent::KeyboardEvent(EventTarget* aOwner,
|
||||
WidgetKeyboardEvent* aEvent)
|
||||
: UIEvent(aOwner, aPresContext,
|
||||
aEvent ? aEvent : new WidgetKeyboardEvent(false, 0, nullptr))
|
||||
, mInitializedByCtor(false)
|
||||
, mInitialzedWhichValue(0)
|
||||
{
|
||||
NS_ASSERTION(mEvent->eventStructType == NS_KEY_EVENT, "event type mismatch");
|
||||
|
||||
@ -139,6 +141,11 @@ KeyboardEvent::GetCharCode(uint32_t* aCharCode)
|
||||
uint32_t
|
||||
KeyboardEvent::CharCode()
|
||||
{
|
||||
// If this event is initialized with ctor, we shouldn't check event type.
|
||||
if (mInitializedByCtor) {
|
||||
return mEvent->AsKeyboardEvent()->charCode;
|
||||
}
|
||||
|
||||
switch (mEvent->message) {
|
||||
case NS_KEY_UP:
|
||||
case NS_KEY_DOWN:
|
||||
@ -160,6 +167,11 @@ KeyboardEvent::GetKeyCode(uint32_t* aKeyCode)
|
||||
uint32_t
|
||||
KeyboardEvent::KeyCode()
|
||||
{
|
||||
// If this event is initialized with ctor, we shouldn't check event type.
|
||||
if (mInitializedByCtor) {
|
||||
return mEvent->AsKeyboardEvent()->keyCode;
|
||||
}
|
||||
|
||||
switch (mEvent->message) {
|
||||
case NS_KEY_UP:
|
||||
case NS_KEY_PRESS:
|
||||
@ -172,6 +184,11 @@ KeyboardEvent::KeyCode()
|
||||
uint32_t
|
||||
KeyboardEvent::Which()
|
||||
{
|
||||
// If this event is initialized with ctor, which can have independent value.
|
||||
if (mInitializedByCtor) {
|
||||
return mInitialzedWhichValue;
|
||||
}
|
||||
|
||||
switch (mEvent->message) {
|
||||
case NS_KEY_UP:
|
||||
case NS_KEY_DOWN:
|
||||
@ -206,6 +223,36 @@ KeyboardEvent::Location()
|
||||
return mEvent->AsKeyboardEvent()->location;
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<KeyboardEvent>
|
||||
KeyboardEvent::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const KeyboardEventInit& aParam,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
nsRefPtr<KeyboardEvent> newEvent =
|
||||
new KeyboardEvent(target, nullptr, nullptr);
|
||||
bool trusted = newEvent->Init(target);
|
||||
aRv = newEvent->InitKeyEvent(aType, aParam.mBubbles, aParam.mCancelable,
|
||||
aParam.mView, aParam.mCtrlKey, aParam.mAltKey,
|
||||
aParam.mShiftKey, aParam.mMetaKey,
|
||||
aParam.mKeyCode, aParam.mCharCode);
|
||||
newEvent->SetTrusted(trusted);
|
||||
newEvent->mDetail = aParam.mDetail;
|
||||
newEvent->mInitializedByCtor = true;
|
||||
newEvent->mInitialzedWhichValue = aParam.mWhich;
|
||||
|
||||
WidgetKeyboardEvent* internalEvent = newEvent->mEvent->AsKeyboardEvent();
|
||||
internalEvent->location = aParam.mLocation;
|
||||
internalEvent->mIsRepeat = aParam.mRepeat;
|
||||
internalEvent->mIsComposing = aParam.mIsComposing;
|
||||
internalEvent->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
|
||||
internalEvent->mKeyValue = aParam.mKey;
|
||||
|
||||
return newEvent.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
KeyboardEvent::InitKeyEvent(const nsAString& aType,
|
||||
bool aCanBubble,
|
||||
@ -229,6 +276,28 @@ KeyboardEvent::InitKeyEvent(const nsAString& aType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
KeyboardEvent::InitKeyboardEvent(const nsAString& aType,
|
||||
bool aCanBubble,
|
||||
bool aCancelable,
|
||||
nsIDOMWindow* aView,
|
||||
uint32_t aDetail,
|
||||
const nsAString& aKey,
|
||||
uint32_t aLocation,
|
||||
const nsAString& aModifiersList,
|
||||
bool aRepeat,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
aRv = UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
|
||||
|
||||
WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
|
||||
keyEvent->modifiers = UIEvent::ComputeModifierState(aModifiersList);
|
||||
keyEvent->location = aLocation;
|
||||
keyEvent->mIsRepeat = aRepeat;
|
||||
keyEvent->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
|
||||
keyEvent->mKeyValue = aKey;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -30,6 +30,12 @@ public:
|
||||
// Forward to base class
|
||||
NS_FORWARD_TO_UIEVENT
|
||||
|
||||
static already_AddRefed<KeyboardEvent> Constructor(
|
||||
const GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const KeyboardEventInit& aParam,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
|
||||
{
|
||||
return KeyboardEventBinding::Wrap(aCx, this);
|
||||
@ -62,6 +68,21 @@ public:
|
||||
aCtrlKey, aAltKey, aShiftKey,aMetaKey,
|
||||
aKeyCode, aCharCode);
|
||||
}
|
||||
|
||||
void InitKeyboardEvent(const nsAString& aType,
|
||||
bool aCanBubble, bool aCancelable,
|
||||
nsIDOMWindow* aView, uint32_t aDetail,
|
||||
const nsAString& aKey, uint32_t aLocation,
|
||||
const nsAString& aModifiersList, bool aRepeat,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
// True, if the instance is created with Constructor().
|
||||
bool mInitializedByCtor;
|
||||
// If the instance is created with Constructor(), which may have independent
|
||||
// value. mInitializedWhichValue stores it. I.e., this is invalid when
|
||||
// mInitializedByCtor is false.
|
||||
uint32_t mInitialzedWhichValue;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -188,20 +188,11 @@ const kEventConstructors = {
|
||||
},
|
||||
},
|
||||
KeyEvent: { create: function (aName, aProps) {
|
||||
var e = document.createEvent("keyboardevent");
|
||||
e.initKeyEvent(aName, aProps.bubbles, aProps.cancelable,
|
||||
aProps.view,
|
||||
aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey,
|
||||
aProps.keyCode, aProps.charCode);
|
||||
return e;
|
||||
return new KeyboardEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
KeyboardEvent: { create: function (aName, aProps) {
|
||||
var e = document.createEvent("keyboardevent");
|
||||
e.initKeyEvent(aName, aProps.bubbles, aProps.cancelable,
|
||||
aProps.view, aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey,
|
||||
aProps.keyCode, aProps.charCode);
|
||||
return e;
|
||||
return new KeyboardEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
MediaStreamEvent: { create: function (aName, aProps) {
|
||||
|
@ -20,55 +20,126 @@ SimpleTest.waitForFocus(runTests, window);
|
||||
function testInitializingUntrustedEvent()
|
||||
{
|
||||
const kTests = [
|
||||
{ createEventArg: "KeyboardEvent",
|
||||
// initKeyEvent
|
||||
{ createEventArg: "KeyboardEvent", useInitKeyboardEvent: false,
|
||||
type: "keydown", bubbles: true, cancelable: true, view: null,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00 },
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 0
|
||||
|
||||
{ createEventArg: "keyboardevent",
|
||||
{ createEventArg: "keyboardevent", useInitKeyboardEvent: false,
|
||||
type: "keyup", bubbles: false, cancelable: true, view: window,
|
||||
ctrlKey: true, altKey: false, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x10, charCode: 0x00 },
|
||||
keyCode: 0x10, charCode: 0x00,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 1
|
||||
|
||||
{ createEventArg: "Keyboardevent",
|
||||
{ createEventArg: "Keyboardevent", useInitKeyboardEvent: false,
|
||||
type: "keypess", bubbles: true, cancelable: false, view: null,
|
||||
ctrlKey: false, altKey: true, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x11, charCode: 0x30 },
|
||||
keyCode: 0x11, charCode: 0x30,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 2
|
||||
|
||||
{ createEventArg: "keyboardEvent",
|
||||
{ createEventArg: "keyboardEvent", useInitKeyboardEvent: false,
|
||||
type: "boo", bubbles: false, cancelable: false, view: window,
|
||||
ctrlKey: false, altKey: false, shiftKey: true, metaKey: false,
|
||||
keyCode: 0x30, charCode: 0x40 },
|
||||
keyCode: 0x30, charCode: 0x40,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 3
|
||||
|
||||
{ createEventArg: "KeyEvents",
|
||||
{ createEventArg: "KeyEvents", useInitKeyboardEvent: false,
|
||||
type: "foo", bubbles: true, cancelable: true, view: null,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: true,
|
||||
keyCode: 0x00, charCode: 0x50 },
|
||||
keyCode: 0x00, charCode: 0x50,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 4
|
||||
|
||||
{ createEventArg: "keyevents",
|
||||
{ createEventArg: "keyevents", useInitKeyboardEvent: false,
|
||||
type: "bar", bubbles: false, cancelable: true, view: window,
|
||||
ctrlKey: true, altKey: true, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x60 },
|
||||
keyCode: 0x00, charCode: 0x60,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 5
|
||||
|
||||
{ createEventArg: "Keyevents",
|
||||
{ createEventArg: "Keyevents", useInitKeyboardEvent: false,
|
||||
type: "keydown", bubbles: true, cancelable: false, view: null,
|
||||
ctrlKey: false, altKey: true, shiftKey: false, metaKey: true,
|
||||
keyCode: 0x30, charCode: 0x00 },
|
||||
keyCode: 0x30, charCode: 0x00,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 6
|
||||
|
||||
{ createEventArg: "keyEvents",
|
||||
{ createEventArg: "keyEvents", useInitKeyboardEvent: false,
|
||||
type: "keyup", bubbles: false, cancelable: false, view: window,
|
||||
ctrlKey: true, altKey: false, shiftKey: true, metaKey: false,
|
||||
keyCode: 0x10, charCode: 0x80 },
|
||||
keyCode: 0x10, charCode: 0x80,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 7
|
||||
|
||||
{ createEventArg: "KeyboardEvent",
|
||||
{ createEventArg: "KeyboardEvent", useInitKeyboardEvent: false,
|
||||
type: "keypress", bubbles: false, cancelable: false, view: window,
|
||||
ctrlKey: true, altKey: false, shiftKey: true, metaKey: true,
|
||||
keyCode: 0x10, charCode: 0x80 },
|
||||
keyCode: 0x10, charCode: 0x80,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 8
|
||||
|
||||
{ createEventArg: "KeyboardEvent",
|
||||
{ createEventArg: "KeyboardEvent", useInitKeyboardEvent: false,
|
||||
type: "foo", bubbles: false, cancelable: false, view: window,
|
||||
ctrlKey: true, altKey: true, shiftKey: true, metaKey: true,
|
||||
keyCode: 0x10, charCode: 0x80 },
|
||||
keyCode: 0x10, charCode: 0x80,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 9
|
||||
|
||||
// initKeyboardEvent
|
||||
{ createEventArg: "KeyboardEvent", useInitKeyboardEvent: true,
|
||||
type: "keydown", bubbles: true, cancelable: true, view: null,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "", location: 0, modifiersList: "", repeat: false,
|
||||
}, // 10
|
||||
|
||||
{ createEventArg: "keyboardevent", useInitKeyboardEvent: true,
|
||||
type: "keyup", bubbles: false, cancelable: true, view: window,
|
||||
ctrlKey: true, altKey: false, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 2, key: "Unidentified", location: 1, modifiersList: "Control", repeat: false,
|
||||
}, // 11
|
||||
|
||||
{ createEventArg: "Keyboardevent", useInitKeyboardEvent: true,
|
||||
type: "keypess", bubbles: true, cancelable: false, view: null,
|
||||
ctrlKey: false, altKey: true, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "FooBar", location: 2, modifiersList: "Alt", repeat: true,
|
||||
}, // 12
|
||||
|
||||
{ createEventArg: "keyboardevent", useInitKeyboardEvent: true,
|
||||
type: "foo", bubbles: true, cancelable: true, view: null,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: true,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "a", location: 0, modifiersList: "Meta", repeat: false,
|
||||
}, // 13
|
||||
|
||||
{ createEventArg: "Keyevents", useInitKeyboardEvent: true,
|
||||
type: "", bubbles: false, cancelable: false, view: null,
|
||||
ctrlKey: true, altKey: true, shiftKey: true, metaKey: true,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "3", location: 0, modifiersList: "Control Alt Meta Shift", repeat: true,
|
||||
}, // 14
|
||||
|
||||
{ createEventArg: "keyevents", useInitKeyboardEvent: true,
|
||||
type: "", bubbles: false, cancelable: false, view: null,
|
||||
ctrlKey: false, altKey: false, shiftKey: true, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "3", location: 6, modifiersList: "Shift", repeat: true,
|
||||
}, // 15
|
||||
|
||||
{ createEventArg: "keyevents", useInitKeyboardEvent: true,
|
||||
type: "", bubbles: false, cancelable: false, view: null,
|
||||
ctrlKey: false, altKey: true, shiftKey: false, metaKey: false,
|
||||
keyCode: 0x00, charCode: 0x00,
|
||||
detail: 0, key: "", location: 4, modifiersList: "Shift, Alt", repeat: false,
|
||||
}, // 16
|
||||
];
|
||||
|
||||
const kOtherModifierName = [
|
||||
@ -85,17 +156,23 @@ function testInitializingUntrustedEvent()
|
||||
var description = "testInitializingUntrustedEvent, Index: " + i + ", ";
|
||||
const kTest = kTests[i];
|
||||
var e = document.createEvent(kTest.createEventArg);
|
||||
e.initKeyEvent(kTest.type, kTest.bubbles, kTest.cancelable, kTest.view,
|
||||
kTest.ctrlKey, kTest.altKey, kTest.shiftKey, kTest.metaKey,
|
||||
kTest.keyCode, kTest.charCode);
|
||||
if (kTest.useInitKeyboardEvent) {
|
||||
// IE has extra argument for |.locale|. Calling with it shouldn't cause error for compatibility with IE.
|
||||
e.initKeyboardEvent(kTest.type, kTest.bubbles, kTest.cancelable, kTest.view, kTest.detail,
|
||||
kTest.key, kTest.location, kTest.modifiersList, kTest.repeat, "locale");
|
||||
} else {
|
||||
e.initKeyEvent(kTest.type, kTest.bubbles, kTest.cancelable, kTest.view,
|
||||
kTest.ctrlKey, kTest.altKey, kTest.shiftKey, kTest.metaKey,
|
||||
kTest.keyCode, kTest.charCode);
|
||||
}
|
||||
is(e.toString(), "[object KeyboardEvent]",
|
||||
description + 'class string should be "KeyboardEvent"');
|
||||
|
||||
for (var attr in kTest) {
|
||||
if (attr == "createEventArg") {
|
||||
if (attr == "createEventArg" || attr == "useInitKeyboardEvent" || attr == "modifiersList") {
|
||||
continue;
|
||||
}
|
||||
if (attr == "keyCode") {
|
||||
if (!kTest.useInitKeyboardEvent && attr == "keyCode") {
|
||||
// If this is keydown, keyup of keypress event, keycod must be correct.
|
||||
if (kTest.type == "keydown" || kTest.type == "keyup" || kTest.type == "keypress") {
|
||||
is(e[attr], kTest[attr], description + attr + " returns wrong value");
|
||||
@ -103,7 +180,7 @@ function testInitializingUntrustedEvent()
|
||||
} else {
|
||||
is(e[attr], 0, description + attr + " returns non-zero for invalid event");
|
||||
}
|
||||
} else if (attr == "charCode") {
|
||||
} else if (!kTest.useInitKeyboardEvent && attr == "charCode") {
|
||||
// If this is keydown or keyup event, charCode always 0.
|
||||
if (kTest.type == "keydown" || kTest.type == "keyup") {
|
||||
is(e[attr], 0, description + attr + " returns non-zero for keydown or keyup event");
|
||||
@ -122,9 +199,6 @@ function testInitializingUntrustedEvent()
|
||||
}
|
||||
is(e.isTrusted, false, description + "isTrusted returns wrong value");
|
||||
|
||||
// Currently, there is no way to initialize char and key attribute values.
|
||||
ok(e.key === "", description + "key must return empty string - got " + e.key);
|
||||
|
||||
// getModifierState() tests
|
||||
is(e.getModifierState("Shift"), kTest.shiftKey,
|
||||
description + "getModifierState(\"Shift\") returns wrong value");
|
||||
|
@ -340,6 +340,88 @@ ok(e.cancelable, "Event should be cancelable!");
|
||||
is(e.detail, 0, "detail should be 0");
|
||||
ok(e.isComposing, "isComposing should be true");
|
||||
|
||||
// KeyboardEvent
|
||||
|
||||
try {
|
||||
e = new KeyboardEvent();
|
||||
} catch(exp) {
|
||||
ex = true;
|
||||
}
|
||||
ok(ex, "KeyboardEvent: First parameter is required!");
|
||||
ex = false;
|
||||
|
||||
e = new KeyboardEvent("hello");
|
||||
ok(e.type, "hello", "KeyboardEvent: Wrong event type!");
|
||||
ok(!e.isTrusted, "KeyboardEvent: Event shouldn't be trusted!");
|
||||
ok(!e.bubbles, "KeyboardEvent: Event shouldn't bubble!");
|
||||
ok(!e.cancelable, "KeyboardEvent: Event shouldn't be cancelable!");
|
||||
document.dispatchEvent(e);
|
||||
is(receivedEvent, e, "KeyboardEvent: Wrong event!");
|
||||
|
||||
var keyboardEventProps =
|
||||
[
|
||||
{ bubbles: false },
|
||||
{ cancelable: false },
|
||||
{ view: null },
|
||||
{ detail: 0 },
|
||||
{ key: "" },
|
||||
{ location: 0 },
|
||||
{ ctrlKey: false },
|
||||
{ shiftKey: false },
|
||||
{ altKey: false },
|
||||
{ metaKey: false },
|
||||
{ repeat: false },
|
||||
{ isComposing: false },
|
||||
{ charCode: 0 },
|
||||
{ keyCode: 0 },
|
||||
{ which: 0 },
|
||||
];
|
||||
|
||||
var testKeyboardProps =
|
||||
[
|
||||
{ bubbles: true },
|
||||
{ cancelable: true },
|
||||
{ view: window },
|
||||
{ detail: 1 },
|
||||
{ key: "CustomKey" },
|
||||
{ location: 1 },
|
||||
{ ctrlKey: true },
|
||||
{ shiftKey: true },
|
||||
{ altKey: true },
|
||||
{ metaKey: true },
|
||||
{ repeat: true },
|
||||
{ isComposing: true },
|
||||
{ charCode: 2 },
|
||||
{ keyCode: 3 },
|
||||
{ which: 4 },
|
||||
{ charCode: 5, which: 6 },
|
||||
{ keyCode: 7, which: 8 },
|
||||
{ keyCode: 9, charCode: 10 },
|
||||
{ keyCode: 11, charCode: 12, which: 13 },
|
||||
];
|
||||
|
||||
var defaultKeyboardEventValues = {};
|
||||
for (var i = 0; i < keyboardEventProps.length; ++i) {
|
||||
for (prop in keyboardEventProps[i]) {
|
||||
ok(prop in e, "keyboardEvent: KeyboardEvent doesn't have property " + prop + "!");
|
||||
defaultKeyboardEventValues[prop] = keyboardEventProps[i][prop];
|
||||
}
|
||||
}
|
||||
|
||||
while (testKeyboardProps.length) {
|
||||
var p = testKeyboardProps.shift();
|
||||
e = new KeyboardEvent("foo", p);
|
||||
for (var def in defaultKeyboardEventValues) {
|
||||
if (!(def in p)) {
|
||||
is(e[def], defaultKeyboardEventValues[def],
|
||||
"KeyboardEvent: Wrong default value for " + def + "!");
|
||||
} else {
|
||||
is(e[def], p[def],
|
||||
"KeyboardEvent: Wrong event init value for " + def + "!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PageTransitionEvent
|
||||
|
||||
try {
|
||||
|
@ -608,7 +608,9 @@ IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
|
||||
|
||||
// See if we're currently clearing the storages for this origin. If so then
|
||||
// we pretend that we've already deleted everything.
|
||||
if (quotaManager->IsClearOriginPending(aFileManager->Origin())) {
|
||||
if (quotaManager->IsClearOriginPending(
|
||||
aFileManager->Origin(),
|
||||
Nullable<PersistenceType>(aFileManager->Type()))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2146,7 +2146,8 @@ OpenDatabaseHelper::StartSetVersion()
|
||||
NS_ASSERTION(quotaManager, "This should never be null!");
|
||||
|
||||
rv = quotaManager->AcquireExclusiveAccess(
|
||||
mDatabase, mDatabase->Origin(), helper,
|
||||
mDatabase, mDatabase->Origin(),
|
||||
Nullable<PersistenceType>(mDatabase->Type()), helper,
|
||||
&VersionChangeEventsRunnable::QueueVersionChange<SetVersionHelper>,
|
||||
helper);
|
||||
IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
@ -2176,7 +2177,8 @@ OpenDatabaseHelper::StartDelete()
|
||||
NS_ASSERTION(quotaManager, "This should never be null!");
|
||||
|
||||
rv = quotaManager->AcquireExclusiveAccess(
|
||||
mDatabase, mDatabase->Origin(), helper,
|
||||
mDatabase, mDatabase->Origin(),
|
||||
Nullable<PersistenceType>(mDatabase->Type()), helper,
|
||||
&VersionChangeEventsRunnable::QueueVersionChange<DeleteDatabaseHelper>,
|
||||
helper);
|
||||
IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
@ -73,6 +73,7 @@
|
||||
#include "ipc/nsGUIEventIPC.h"
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "UnitTransforms.h"
|
||||
#include "ClientLayerManager.h"
|
||||
|
||||
#include "nsColorPickerProxy.h"
|
||||
|
||||
@ -105,6 +106,9 @@ static bool sCpowsEnabled = false;
|
||||
static int32_t sActiveDurationMs = 10;
|
||||
static bool sActiveDurationMsSet = false;
|
||||
|
||||
typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
|
||||
static TabChildMap* sTabChildren;
|
||||
|
||||
TabChildBase::TabChildBase()
|
||||
: mOldViewportWidth(0.0f)
|
||||
, mContentDocumentIsDisplayed(false)
|
||||
@ -669,6 +673,7 @@ TabChild::TabChild(ContentChild* aManager, const TabContext& aContext, uint32_t
|
||||
, mRemoteFrame(nullptr)
|
||||
, mManager(aManager)
|
||||
, mChromeFlags(aChromeFlags)
|
||||
, mLayersId(0)
|
||||
, mOuterRect(0, 0, 0, 0)
|
||||
, mActivePointerId(-1)
|
||||
, mTapHoldTimer(nullptr)
|
||||
@ -1293,6 +1298,17 @@ TabChild::DestroyWindow()
|
||||
mRemoteFrame->Destroy();
|
||||
mRemoteFrame = nullptr;
|
||||
}
|
||||
|
||||
|
||||
if (mLayersId != 0) {
|
||||
MOZ_ASSERT(sTabChildren);
|
||||
sTabChildren->Remove(mLayersId);
|
||||
if (!sTabChildren->Count()) {
|
||||
delete sTabChildren;
|
||||
sTabChildren = nullptr;
|
||||
}
|
||||
mLayersId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
@ -2387,6 +2403,13 @@ TabChild::InitRenderingState()
|
||||
ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier);
|
||||
|
||||
mRemoteFrame = remoteFrame;
|
||||
if (id != 0) {
|
||||
if (!sTabChildren) {
|
||||
sTabChildren = new TabChildMap;
|
||||
}
|
||||
sTabChildren->Put(id, this);
|
||||
mLayersId = id;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
@ -2583,6 +2606,26 @@ TabChild::GetFrom(nsIPresShell* aPresShell)
|
||||
return GetFrom(docShell);
|
||||
}
|
||||
|
||||
TabChild*
|
||||
TabChild::GetFrom(uint64_t aLayersId)
|
||||
{
|
||||
if (!sTabChildren) {
|
||||
return nullptr;
|
||||
}
|
||||
return sTabChildren->Get(aLayersId);
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::DidComposite()
|
||||
{
|
||||
MOZ_ASSERT(mWidget);
|
||||
MOZ_ASSERT(mWidget->GetLayerManager());
|
||||
MOZ_ASSERT(mWidget->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT);
|
||||
|
||||
ClientLayerManager *manager = static_cast<ClientLayerManager*>(mWidget->GetLayerManager());
|
||||
manager->DidComposite();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TabChild::OnShowTooltip(int32_t aXCoords, int32_t aYCoords, const char16_t *aTipText)
|
||||
{
|
||||
|
@ -430,6 +430,9 @@ public:
|
||||
}
|
||||
|
||||
static TabChild* GetFrom(nsIPresShell* aPresShell);
|
||||
static TabChild* GetFrom(uint64_t aLayersId);
|
||||
|
||||
void DidComposite();
|
||||
|
||||
static inline TabChild*
|
||||
GetFrom(nsIDOMWindow* aWindow)
|
||||
@ -514,6 +517,7 @@ private:
|
||||
RenderFrameChild* mRemoteFrame;
|
||||
nsRefPtr<ContentChild> mManager;
|
||||
uint32_t mChromeFlags;
|
||||
uint64_t mLayersId;
|
||||
nsIntRect mOuterRect;
|
||||
// When we're tracking a possible tap gesture, this is the "down"
|
||||
// point of the touchstart.
|
||||
|
@ -55,6 +55,28 @@ PersistenceTypeFromText(const nsACString& aText)
|
||||
MOZ_ASSUME_UNREACHABLE("Should never get here!");
|
||||
}
|
||||
|
||||
inline nsresult
|
||||
NullablePersistenceTypeFromText(const nsACString& aText,
|
||||
Nullable<PersistenceType> *aPersistenceType)
|
||||
{
|
||||
if (aText.IsVoid()) {
|
||||
*aPersistenceType = Nullable<PersistenceType>();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aText.EqualsLiteral("persistent")) {
|
||||
*aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aText.EqualsLiteral("temporary")) {
|
||||
*aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
inline mozilla::dom::StorageType
|
||||
PersistenceTypeToStorage(PersistenceType aPersistenceType)
|
||||
{
|
||||
|
@ -188,8 +188,10 @@ class OriginClearRunnable MOZ_FINAL : public nsRunnable,
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern)
|
||||
OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern,
|
||||
Nullable<PersistenceType> aPersistenceType)
|
||||
: mOriginOrPattern(aOriginOrPattern),
|
||||
mPersistenceType(aPersistenceType),
|
||||
mCallbackState(Pending)
|
||||
{ }
|
||||
|
||||
@ -228,6 +230,7 @@ public:
|
||||
|
||||
private:
|
||||
OriginOrPatternString mOriginOrPattern;
|
||||
Nullable<PersistenceType> mPersistenceType;
|
||||
CallbackState mCallbackState;
|
||||
};
|
||||
|
||||
@ -2280,6 +2283,7 @@ NS_IMETHODIMP
|
||||
QuotaManager::ClearStoragesForURI(nsIURI* aURI,
|
||||
uint32_t aAppId,
|
||||
bool aInMozBrowserOnly,
|
||||
const nsACString& aPersistenceType,
|
||||
uint8_t aOptionalArgCount)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
@ -2302,19 +2306,23 @@ QuotaManager::ClearStoragesForURI(nsIURI* aURI,
|
||||
nsAutoCString pattern;
|
||||
GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern);
|
||||
|
||||
Nullable<PersistenceType> persistenceType;
|
||||
rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If there is a pending or running clear operation for this origin, return
|
||||
// immediately.
|
||||
if (IsClearOriginPending(pattern)) {
|
||||
if (IsClearOriginPending(pattern, persistenceType)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
|
||||
|
||||
// Queue up the origin clear runnable.
|
||||
nsRefPtr<OriginClearRunnable> runnable = new OriginClearRunnable(oops);
|
||||
nsRefPtr<OriginClearRunnable> runnable =
|
||||
new OriginClearRunnable(oops, persistenceType);
|
||||
|
||||
rv = WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
|
||||
runnable);
|
||||
rv = WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
runnable->AdvanceState();
|
||||
@ -2324,10 +2332,13 @@ QuotaManager::ClearStoragesForURI(nsIURI* aURI,
|
||||
matches.Find(mLiveStorages, pattern);
|
||||
|
||||
for (uint32_t index = 0; index < matches.Length(); index++) {
|
||||
// We need to grab references to any live storages here to prevent them
|
||||
// from dying while we invalidate them.
|
||||
nsCOMPtr<nsIOfflineStorage> storage = matches[index];
|
||||
storage->Invalidate();
|
||||
if (persistenceType.IsNull() ||
|
||||
matches[index]->Type() == persistenceType.Value()) {
|
||||
// We need to grab references to any live storages here to prevent them
|
||||
// from dying while we invalidate them.
|
||||
nsCOMPtr<nsIOfflineStorage> storage = matches[index];
|
||||
storage->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// After everything has been invalidated the helper should be dispatched to
|
||||
@ -2640,6 +2651,7 @@ QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
|
||||
|
||||
nsresult
|
||||
QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern,
|
||||
Nullable<PersistenceType> aPersistenceType,
|
||||
nsIOfflineStorage* aStorage,
|
||||
AcquireListener* aListener,
|
||||
WaitingOnStoragesCallback aCallback,
|
||||
@ -2649,16 +2661,9 @@ QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern,
|
||||
NS_ASSERTION(aListener, "Need a listener!");
|
||||
|
||||
// Find the right SynchronizedOp.
|
||||
SynchronizedOp* op;
|
||||
if (aStorage) {
|
||||
op = FindSynchronizedOp(aPattern,
|
||||
Nullable<PersistenceType>(aStorage->Type()),
|
||||
aStorage->Id());
|
||||
}
|
||||
else {
|
||||
op = FindSynchronizedOp(aPattern, Nullable<PersistenceType>(),
|
||||
EmptyCString());
|
||||
}
|
||||
SynchronizedOp* op =
|
||||
FindSynchronizedOp(aPattern, aPersistenceType,
|
||||
aStorage ? aStorage->Id() : EmptyCString());
|
||||
|
||||
NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
|
||||
NS_ASSERTION(!op->mListener, "SynchronizedOp already has a listener?!?");
|
||||
@ -2840,20 +2845,23 @@ QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly)
|
||||
nsAutoCString pattern;
|
||||
GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern);
|
||||
|
||||
// Clear both temporary and persistent storages.
|
||||
Nullable<PersistenceType> persistenceType;
|
||||
|
||||
// If there is a pending or running clear operation for this app, return
|
||||
// immediately.
|
||||
if (IsClearOriginPending(pattern)) {
|
||||
if (IsClearOriginPending(pattern, persistenceType)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
|
||||
|
||||
// Queue up the origin clear runnable.
|
||||
nsRefPtr<OriginClearRunnable> runnable = new OriginClearRunnable(oops);
|
||||
nsRefPtr<OriginClearRunnable> runnable =
|
||||
new OriginClearRunnable(oops, persistenceType);
|
||||
|
||||
nsresult rv =
|
||||
WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
|
||||
runnable);
|
||||
WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
runnable->AdvanceState();
|
||||
@ -3507,8 +3515,9 @@ OriginClearRunnable::Run()
|
||||
// Now we have to wait until the thread pool is done with all of the
|
||||
// storages we care about.
|
||||
nsresult rv =
|
||||
quotaManager->AcquireExclusiveAccess(mOriginOrPattern, this,
|
||||
InvalidateOpenedStorages, nullptr);
|
||||
quotaManager->AcquireExclusiveAccess(mOriginOrPattern, mPersistenceType,
|
||||
this, InvalidateOpenedStorages,
|
||||
nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
@ -3519,9 +3528,12 @@ OriginClearRunnable::Run()
|
||||
|
||||
AdvanceState();
|
||||
|
||||
DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
|
||||
|
||||
DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
|
||||
if (mPersistenceType.IsNull()) {
|
||||
DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
|
||||
DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
|
||||
} else {
|
||||
DeleteFiles(quotaManager, mPersistenceType.Value());
|
||||
}
|
||||
|
||||
// Now dispatch back to the main thread.
|
||||
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
|
||||
@ -3536,8 +3548,7 @@ OriginClearRunnable::Run()
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
// Tell the QuotaManager that we're done.
|
||||
quotaManager->AllowNextSynchronizedOp(mOriginOrPattern,
|
||||
Nullable<PersistenceType>(),
|
||||
quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, mPersistenceType,
|
||||
EmptyCString());
|
||||
|
||||
return NS_OK;
|
||||
@ -3863,7 +3874,8 @@ ResetOrClearRunnable::Run()
|
||||
// Now we have to wait until the thread pool is done with all of the
|
||||
// storages we care about.
|
||||
nsresult rv =
|
||||
quotaManager->AcquireExclusiveAccess(NullCString(), this,
|
||||
quotaManager->AcquireExclusiveAccess(NullCString(),
|
||||
Nullable<PersistenceType>(), this,
|
||||
InvalidateOpenedStorages, nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
@ -198,23 +198,25 @@ public:
|
||||
nsresult
|
||||
AcquireExclusiveAccess(nsIOfflineStorage* aStorage,
|
||||
const nsACString& aOrigin,
|
||||
Nullable<PersistenceType> aPersistenceType,
|
||||
AcquireListener* aListener,
|
||||
WaitingOnStoragesCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_ASSERTION(aStorage, "Need a storage here!");
|
||||
return AcquireExclusiveAccess(aOrigin, aStorage, aListener, aCallback,
|
||||
aClosure);
|
||||
return AcquireExclusiveAccess(aOrigin, aPersistenceType, aStorage,
|
||||
aListener, aCallback, aClosure);
|
||||
}
|
||||
|
||||
nsresult
|
||||
AcquireExclusiveAccess(const nsACString& aOrigin,
|
||||
Nullable<PersistenceType> aPersistenceType,
|
||||
AcquireListener* aListener,
|
||||
WaitingOnStoragesCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
return AcquireExclusiveAccess(aOrigin, nullptr, aListener, aCallback,
|
||||
aClosure);
|
||||
return AcquireExclusiveAccess(aOrigin, aPersistenceType, nullptr,
|
||||
aListener, aCallback, aClosure);
|
||||
}
|
||||
|
||||
void
|
||||
@ -223,10 +225,10 @@ public:
|
||||
const nsACString& aId);
|
||||
|
||||
bool
|
||||
IsClearOriginPending(const nsACString& aPattern)
|
||||
IsClearOriginPending(const nsACString& aPattern,
|
||||
Nullable<PersistenceType> aPersistenceType)
|
||||
{
|
||||
return !!FindSynchronizedOp(aPattern, Nullable<PersistenceType>(),
|
||||
EmptyCString());
|
||||
return !!FindSynchronizedOp(aPattern, aPersistenceType, EmptyCString());
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -369,6 +371,7 @@ private:
|
||||
|
||||
nsresult
|
||||
AcquireExclusiveAccess(const nsACString& aOrigin,
|
||||
Nullable<PersistenceType> aPersistenceType,
|
||||
nsIOfflineStorage* aStorage,
|
||||
AcquireListener* aListener,
|
||||
WaitingOnStoragesCallback aCallback,
|
||||
|
@ -10,7 +10,7 @@ interface nsIQuotaRequest;
|
||||
interface nsIURI;
|
||||
interface nsIUsageCallback;
|
||||
|
||||
[scriptable, builtinclass, uuid(f19a03ae-e97d-41e9-95dd-681b910c4093)]
|
||||
[scriptable, builtinclass, uuid(2968fcd5-1872-4ddc-8c16-62b27e357f31)]
|
||||
interface nsIQuotaManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -51,7 +51,8 @@ interface nsIQuotaManager : nsISupports
|
||||
void
|
||||
clearStoragesForURI(in nsIURI aURI,
|
||||
[optional] in unsigned long aAppId,
|
||||
[optional] in boolean aInMozBrowserOnly);
|
||||
[optional] in boolean aInMozBrowserOnly,
|
||||
[optional] in ACString aPersistenceType);
|
||||
|
||||
/**
|
||||
* Resets quota and storage management. This can be used to force
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
interface WindowProxy;
|
||||
|
||||
[Constructor(DOMString typeArg, optional KeyboardEventInit keyboardEventInitDict)]
|
||||
interface KeyboardEvent : UIEvent
|
||||
{
|
||||
readonly attribute unsigned long charCode;
|
||||
@ -30,6 +31,34 @@ interface KeyboardEvent : UIEvent
|
||||
readonly attribute boolean isComposing;
|
||||
|
||||
readonly attribute DOMString key;
|
||||
|
||||
[Throws]
|
||||
void initKeyboardEvent(DOMString typeArg,
|
||||
boolean bubblesArg,
|
||||
boolean cancelableArg,
|
||||
WindowProxy? viewArg,
|
||||
long detailArg,
|
||||
DOMString keyArg,
|
||||
unsigned long locationArg,
|
||||
DOMString modifiersListArg,
|
||||
boolean repeatArg);
|
||||
};
|
||||
|
||||
dictionary KeyboardEventInit : UIEventInit
|
||||
{
|
||||
DOMString key = "";
|
||||
unsigned long location = 0;
|
||||
boolean ctrlKey = false;
|
||||
boolean shiftKey = false;
|
||||
boolean altKey = false;
|
||||
boolean metaKey = false;
|
||||
boolean repeat = false;
|
||||
boolean isComposing = false;
|
||||
|
||||
// legacy attributes
|
||||
unsigned long charCode = 0;
|
||||
unsigned long keyCode = 0;
|
||||
unsigned long which = 0;
|
||||
};
|
||||
|
||||
// Mozilla extensions
|
||||
|
@ -957,6 +957,8 @@ GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
|
||||
}
|
||||
JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
|
||||
if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
|
||||
NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
|
||||
"key. XBL binding will fail for this element.");
|
||||
return nullptr;
|
||||
}
|
||||
return entry;
|
||||
@ -997,6 +999,9 @@ nsXBLBinding::DoInitJSClass(JSContext *cx,
|
||||
JSAutoCompartment innerAC(cx, xblScope);
|
||||
holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
|
||||
}
|
||||
if (NS_WARN_IF(!holder)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
js::AssertSameCompartment(holder, xblScope);
|
||||
JSAutoCompartment ac(cx, holder);
|
||||
|
||||
|
@ -22,7 +22,7 @@ SurfaceStream::ChooseGLStreamType(SurfaceStream::OMTC omtc,
|
||||
if (preserveBuffer)
|
||||
return SurfaceStreamType::TripleBuffer_Copy;
|
||||
else
|
||||
return SurfaceStreamType::TripleBuffer;
|
||||
return SurfaceStreamType::TripleBuffer_Async;
|
||||
} else {
|
||||
if (preserveBuffer)
|
||||
return SurfaceStreamType::SingleBuffer;
|
||||
@ -43,6 +43,9 @@ SurfaceStream::CreateForType(SurfaceStreamType type, mozilla::gl::GLContext* glC
|
||||
case SurfaceStreamType::TripleBuffer_Copy:
|
||||
result = new SurfaceStream_TripleBuffer_Copy(prevStream);
|
||||
break;
|
||||
case SurfaceStreamType::TripleBuffer_Async:
|
||||
result = new SurfaceStream_TripleBuffer_Async(prevStream);
|
||||
break;
|
||||
case SurfaceStreamType::TripleBuffer:
|
||||
result = new SurfaceStream_TripleBuffer(prevStream);
|
||||
break;
|
||||
@ -429,7 +432,9 @@ SurfaceStream_TripleBuffer::SwapProducer(SurfaceFactory* factory,
|
||||
if (mProducer) {
|
||||
RecycleScraps(factory);
|
||||
|
||||
if (mStaging)
|
||||
// If WaitForCompositor succeeds, mStaging has moved to mConsumer.
|
||||
// If it failed, we might have to scrap it.
|
||||
if (mStaging && !WaitForCompositor())
|
||||
Scrap(mStaging);
|
||||
|
||||
MOZ_ASSERT(!mStaging);
|
||||
@ -456,5 +461,29 @@ SurfaceStream_TripleBuffer::SwapConsumer_NoWait()
|
||||
return mConsumer;
|
||||
}
|
||||
|
||||
SurfaceStream_TripleBuffer_Async::SurfaceStream_TripleBuffer_Async(SurfaceStream* prevStream)
|
||||
: SurfaceStream_TripleBuffer(SurfaceStreamType::TripleBuffer_Async, prevStream)
|
||||
{
|
||||
}
|
||||
|
||||
SurfaceStream_TripleBuffer_Async::~SurfaceStream_TripleBuffer_Async()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
SurfaceStream_TripleBuffer_Async::WaitForCompositor()
|
||||
{
|
||||
PROFILER_LABEL("SurfaceStream_TripleBuffer_Async", "WaitForCompositor");
|
||||
|
||||
// We are assumed to be locked
|
||||
while (mStaging) {
|
||||
if (!NS_SUCCEEDED(mMonitor.Wait(PR_MillisecondsToInterval(100)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace gfx */
|
||||
} /* namespace mozilla */
|
||||
|
@ -191,6 +191,9 @@ protected:
|
||||
SharedSurface* mStaging;
|
||||
SharedSurface* mConsumer;
|
||||
|
||||
// Returns true if we were able to wait, false if not
|
||||
virtual bool WaitForCompositor() { return false; }
|
||||
|
||||
// To support subclasses initializing the mType.
|
||||
SurfaceStream_TripleBuffer(SurfaceStreamType type, SurfaceStream* prevStream);
|
||||
|
||||
@ -214,6 +217,18 @@ public:
|
||||
virtual void SurrenderSurfaces(SharedSurface*& producer, SharedSurface*& consumer);
|
||||
};
|
||||
|
||||
class SurfaceStream_TripleBuffer_Async
|
||||
: public SurfaceStream_TripleBuffer
|
||||
{
|
||||
protected:
|
||||
virtual bool WaitForCompositor() MOZ_OVERRIDE;
|
||||
|
||||
public:
|
||||
SurfaceStream_TripleBuffer_Async(SurfaceStream* prevStream);
|
||||
virtual ~SurfaceStream_TripleBuffer_Async();
|
||||
};
|
||||
|
||||
|
||||
} /* namespace gfx */
|
||||
} /* namespace mozilla */
|
||||
|
||||
|
@ -87,6 +87,7 @@ MOZ_END_ENUM_CLASS(SharedSurfaceType)
|
||||
MOZ_BEGIN_ENUM_CLASS(SurfaceStreamType, uint8_t)
|
||||
SingleBuffer,
|
||||
TripleBuffer_Copy,
|
||||
TripleBuffer_Async,
|
||||
TripleBuffer,
|
||||
Max
|
||||
MOZ_END_ENUM_CLASS(SurfaceStreamType)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user