Merge m-c to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-04-15 15:16:00 +02:00
commit a8c60dba60
214 changed files with 23176 additions and 707 deletions

View File

@ -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));

View File

@ -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 });
}

View File

@ -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");
};
*/

View File

@ -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 });
}

View File

@ -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);
};
*/

View File

@ -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")
});
},

View File

@ -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);

View File

@ -67,3 +67,8 @@ function load(sandbox, uri) {
}
}
exports.load = load;
/**
* Forces the given `sandbox` to be freed immediately.
*/
exports.nuke = Cu.nukeSandbox

View File

@ -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);

View File

@ -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);
});

View File

@ -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".

View File

@ -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);

View File

@ -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"
}
}
});

View File

@ -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.'

View File

@ -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);

View File

@ -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();
}
});

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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");
};

View 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);

View File

@ -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');

View File

@ -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');

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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]);

View File

@ -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>

View File

@ -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);

View File

@ -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) {

View File

@ -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();
},

View File

@ -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]

View File

@ -31,4 +31,6 @@ add_task(function*() {
CustomizableUI.destroyWidget(BUTTONID);
CustomizableUI.unregisterArea(TOOLBARID, true);
toolbar.remove();
gAddedToolbars.clear();
});

View File

@ -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.");
});

View File

@ -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

View File

@ -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);
}
}

View File

@ -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',

View File

@ -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"

View File

@ -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"

View File

@ -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"
}
]

View File

@ -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

View File

@ -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
*/

View File

@ -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 {

View File

@ -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);

View File

@ -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

View File

@ -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'):

View File

@ -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

View File

@ -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)
{

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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>

View 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");
}

View 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>

View File

@ -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]

View File

@ -33,7 +33,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -65,7 +65,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -60,7 +60,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -44,7 +44,7 @@ function runTest() {
else
renderBackup(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -42,7 +42,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -41,7 +41,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -41,7 +41,7 @@ function runTest() {
else
renderFailure(canvas);
rAF(testComplete);
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -42,7 +42,7 @@ function runTest() {
else
renderFailure(canvas);
testComplete();
waitForComposite(testComplete);
}
function testComplete() {

View File

@ -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);
}

View 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>

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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");

View File

@ -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 {

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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.

View File

@ -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)
{

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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 */

View File

@ -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 */

View File

@ -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