Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-11-26 16:56:39 +01:00
commit 0a41d103c4
427 changed files with 7364 additions and 4187 deletions

View File

@ -38,6 +38,6 @@ exports.encode = function (data, charset) {
if (isUTF8(charset))
return btoa(unescape(encodeURIComponent(data)))
data = String.fromCharCode(...[(c.charCodeAt(0) & 0xff) for (c of data)]);
data = String.fromCharCode(...Array.from(data, c => (c.charCodeAt(0) & 0xff)));
return btoa(data);
}

View File

@ -307,7 +307,7 @@ function getItemWorkerForWindow(item, window) {
var RemoteItem = Class({
initialize: function(options, manager) {
this.id = options.id;
this.contexts = [instantiateContext(c) for (c of options.contexts)];
this.contexts = options.contexts.map(instantiateContext);
this.contentScript = options.contentScript;
this.contentScriptFile = options.contentScriptFile;

View File

@ -90,7 +90,7 @@ function makeChildOptions(options) {
function makeStringArray(arrayOrValue) {
if (!arrayOrValue)
return [];
return [String(v) for (v of [].concat(arrayOrValue))];
return [].concat(arrayOrValue).map(String);
}
return {

View File

@ -355,7 +355,7 @@ function itemActivated(item, clickedNode) {
function serializeItem(item) {
return {
id: internal(item).id,
contexts: [c.serialize() for (c of item.context)],
contexts: item.context.map(c => c.serialize()),
contentScript: item.contentScript,
contentScriptFile: item.contentScriptFile,
};

View File

@ -380,7 +380,7 @@ TestRunner.prototype = {
name: this.test.name,
passed: this.test.passed,
failed: this.test.failed,
errors: [error for (error in this.test.errors)].join(", ")
errors: Object.keys(this.test.errors).join(", ")
});
if (this.onDone !== null) {

View File

@ -193,7 +193,7 @@ function showResults() {
var data = ref.__url__ ? ref.__url__ : ref;
var warning = data == "[object Object]"
? "[object " + data.constructor.name + "(" +
[p for (p in data)].join(", ") + ")]"
Object.keys(data).join(", ") + ")]"
: data;
console.warn("LEAK", warning, info.bin);
}
@ -461,8 +461,7 @@ var consoleListener = {
testConsole.error(message);
return;
}
var pointless = [err for (err of POINTLESS_ERRORS)
if (message.indexOf(err) >= 0)];
var pointless = POINTLESS_ERRORS.filter(err => message.indexOf(err) >= 0);
if (pointless.length == 0 && message)
testConsole.log(message);
}

View File

@ -62,7 +62,7 @@ var dispatcher = _ => {
// starting a dispatch loop, in order to ignore timers registered
// in side effect to dispatch while also skipping immediates that
// were removed in side effect.
let ids = [id for ([id] of immediates)];
let ids = [...immediates.keys()];
for (let id of ids) {
let immediate = immediates.get(id);
if (immediate) {

View File

@ -65,12 +65,9 @@ exports.testBasename = function(assert) {
exports.testList = function(assert) {
let list = file.list(profilePath);
let found = [ true for (name of list)
if (name === fileNameInProfile) ];
let found = list.filter(name => name === fileNameInProfile);
if (found.length > 1)
assert.fail("a dir can't contain two files of the same name!");
assert.equal(found[0], true, "file.list() should work");
assert.equal(found.length, 1, "file.list() should work");
assert.throws(function() {
file.list(filePathInProfile);

View File

@ -174,6 +174,7 @@
@RESPATH@/components/dom_audiochannel.xpt
@RESPATH@/components/dom_base.xpt
@RESPATH@/components/dom_system.xpt
@RESPATH@/components/dom_workers.xpt
#ifdef MOZ_WIDGET_GONK
@RESPATH@/components/dom_wifi.xpt
@RESPATH@/components/dom_system_gonk.xpt

View File

@ -1478,14 +1478,14 @@ pref("browser.uiCustomization.debug", false);
pref("browser.uiCustomization.state", "");
// The remote content URL shown for FxA signup. Must use HTTPS.
pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v1");
pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v2");
// The URL where remote content that forces re-authentication for Firefox Accounts
// should be fetched. Must use HTTPS.
pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v1");
pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v2");
// The remote content URL shown for signin in. Must use HTTPS.
pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v1");
pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v2");
// The remote content URL where FxAccountsWebChannel messages originate.
pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com/");

View File

@ -472,18 +472,8 @@ var LoopUI;
return;
}
// Create the menu that is shown when the menu-button' dropmarker is clicked
// inside the notification bar.
let menuPopup = document.createElementNS(kNSXUL, "menupopup");
let menuItem = menuPopup.appendChild(document.createElementNS(kNSXUL, "menuitem"));
menuItem.setAttribute("label", this._getString("infobar_menuitem_dontshowagain_label"));
menuItem.setAttribute("accesskey", this._getString("infobar_menuitem_dontshowagain_accesskey"));
menuItem.addEventListener("command", () => {
// We're being told to hide the bar permanently.
this._hideBrowserSharingInfoBar(true);
});
let box = gBrowser.getNotificationBox();
let paused = false;
let bar = box.appendNotification(
this._getString("infobar_screenshare_browser_message"),
kBrowserSharingNotificationId,
@ -491,13 +481,28 @@ var LoopUI;
null,
box.PRIORITY_WARNING_LOW,
[{
label: this._getString("infobar_button_gotit_label"),
accessKey: this._getString("infobar_button_gotit_accesskey"),
type: "menu-button",
popup: menuPopup,
anchor: "dropmarker",
label: this._getString("infobar_button_pause_label"),
accessKey: this._getString("infobar_button_pause_accesskey"),
isDefault: false,
callback: (event, buttonInfo, buttonNode) => {
paused = !paused;
bar.label = paused ? this._getString("infobar_screenshare_paused_browser_message") :
this._getString("infobar_screenshare_browser_message");
bar.classList.toggle("paused", paused);
buttonNode.label = paused ? this._getString("infobar_button_resume_label") :
this._getString("infobar_button_pause_label");
buttonNode.accessKey = paused ? this._getString("infobar_button_resume_accesskey") :
this._getString("infobar_button_pause_accesskey");
return true;
}
},
{
label: this._getString("infobar_button_stop_label"),
accessKey: this._getString("infobar_button_stop_accesskey"),
isDefault: true,
callback: () => {
this._hideBrowserSharingInfoBar();
LoopUI.MozLoopService.hangupAllChatWindows();
}
}]
);

View File

@ -1,5 +0,0 @@
<!DOCTYPE html [
<!ENTITY % passwordManagerDTD SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
%passwordManagerDTD;
]>
<window>&savedLogins.title;</window>

View File

@ -25,7 +25,6 @@ support-files =
offlineEvent.html
subtst_contextmenu.html
video.ogg
bug_1182546.xml
[test_bug364677.html]
[test_bug395533.html]
@ -39,4 +38,3 @@ skip-if = e10s
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
[test_offline_gzip.html]
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
[test_bug1182546.html]

View File

@ -1,35 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1182546
-->
<head>
<title>Bug 1182546 - Test block loading DTD from random page</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<iframe id="testframe" src="bug_1182546.xml"></iframe>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
// make sure the DTD loader (nsExpatDriver) prevents accessing chrome: from random pages
var childNodes = testframe.contentDocument.documentElement.childNodes;
// make sure '&savedLogins.title;' from bug_1182546.xml does not translate into 'Saved Logins'
// the URL 'chrome://passwordmgr/locale/passwordManager.dtd' should not be accessible from content
var nodeValue = childNodes[0].nodeValue;
isnot(nodeValue, "Saved Logins",
"expatDriver should prevent accessing &savedLogins.title;");
ok(nodeValue.startsWith("XML Parsing Error: undefined entity"),
"expatDriver should not allow accessing chrome:");
});
addLoadEvent(SimpleTest.finish);
</script>
</body>
</html>

View File

@ -202,6 +202,31 @@ DistributionCustomizer.prototype = {
PlacesUtils.annotations.EXPIRE_NEVER);
}
if (item.icon && item.iconData) {
try {
let faviconURI = this._makeURI(item.icon);
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI, item.iconData, 0,
Services.scriptSecurityManager.getSystemPrincipal());
PlacesUtils.favicons.setAndFetchFaviconForPage(
this._makeURI(item.link), faviconURI, false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
} catch(e) {
Cu.reportError(e);
}
}
if (item.keyword) {
try {
yield PlacesUtils.keywords.insert({ keyword: item.keyword,
url: item.link });
} catch(e) {
Cu.reportError(e);
}
}
break;
}
}

View File

@ -6,6 +6,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
const INTEGER = /^[1-9]\d*$/;
var {
EventManager,
} = ExtensionUtils;
@ -18,38 +22,48 @@ var {
// Manages icon details for toolbar buttons in the |pageAction| and
// |browserAction| APIs.
global.IconDetails = {
// Accepted icon sizes.
SIZES: ["19", "38"],
// Normalizes the various acceptable input formats into an object
// with two properties, "19" and "38", containing icon URLs.
// with icon size as key and icon URL as value.
//
// If a context is specified (function is called from an extension):
// Throws an error if an invalid icon size was provided or the
// extension is not allowed to load the specified resources.
//
// If no context is specified, instead of throwing an error, this
// function simply logs a warning message.
normalize(details, extension, context=null, localize=false) {
let result = {};
if (details.imageData) {
let imageData = details.imageData;
try {
if (details.imageData) {
let imageData = details.imageData;
if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) {
imageData = {"19": imageData};
}
if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) {
imageData = {"19": imageData};
}
for (let size of Object.keys(imageData)) {
if (!INTEGER.test(size)) {
throw new Error(`Invalid icon size ${size}, must be an integer`);
}
for (let size of this.SIZES) {
if (size in imageData) {
result[size] = this.convertImageDataToPNG(imageData[size], context);
}
}
}
if (details.path) {
let path = details.path;
if (typeof path != "object") {
path = {"19": path};
}
if (details.path) {
let path = details.path;
if (typeof path != "object") {
path = {"19": path};
}
let baseURI = context ? context.uri : extension.baseURI;
let baseURI = context ? context.uri : extension.baseURI;
for (let size of Object.keys(path)) {
if (!INTEGER.test(size)) {
throw new Error(`Invalid icon size ${size}, must be an integer`);
}
for (let size of this.SIZES) {
if (size in path) {
let url = path[size];
if (localize) {
url = extension.localize(url);
@ -60,25 +74,23 @@ global.IconDetails = {
// The Chrome documentation specifies these parameters as
// relative paths. We currently accept absolute URLs as well,
// which means we need to check that the extension is allowed
// to load them.
try {
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
extension.principal, url,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
} catch (e) {
if (context) {
throw e;
}
// If there's no context, it's because we're handling this
// as a manifest directive. Log a warning rather than
// raising an error, but don't accept the URL in any case.
extension.manifestError(`Access to URL '${url}' denied`);
continue;
}
// to load them. This will throw an error if it's not allowed.
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
extension.principal, url,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
result[size] = url;
}
}
} catch (e) {
// Function is called from extension code, delegate error.
if (context) {
throw e;
}
// If there's no context, it's because we're handling this
// as a manifest directive. Log a warning rather than
// raising an error.
extension.manifestError(`Invalid icon data: ${e}`);
}
return result;
@ -89,12 +101,7 @@ global.IconDetails = {
getURL(icons, window, extension) {
const DEFAULT = "chrome://browser/content/extension.svg";
// Use the higher resolution image if we're doing any up-scaling
// for high resolution monitors.
let res = window.devicePixelRatio;
let size = res > 1 ? "38" : "19";
return icons[size] || icons["19"] || icons["38"] || DEFAULT;
return AddonManager.getPreferredIconURL({icons: icons}, 18, window) || DEFAULT;
},
convertImageDataToPNG(imageData, context) {

View File

@ -101,6 +101,29 @@ add_task(function* testDetailsObjects() {
resolutions: {
"1": imageData.red.url,
"2": browser.runtime.getURL("data/a.png"), } },
// Various resolutions
{ details: { "path": { "18": "a.png", "32": "a-x2.png" } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a-x2.png"), } },
{ details: { "path": { "16": "16.png", "100": "100.png" } },
resolutions: {
"1": browser.runtime.getURL("data/100.png"),
"2": browser.runtime.getURL("data/100.png"), } },
{ details: { "path": { "2": "2.png"} },
resolutions: {
"1": browser.runtime.getURL("data/2.png"),
"2": browser.runtime.getURL("data/2.png"), } },
{ details: { "path": {
"6": "6.png",
"18": "18.png",
"32": "32.png",
"48": "48.png",
"128": "128.png" } },
resolutions: {
"1": browser.runtime.getURL("data/18.png"),
"2": browser.runtime.getURL("data/48.png"), } },
];
// Allow serializing ImageData objects for logging.
@ -197,6 +220,62 @@ add_task(function* testDetailsObjects() {
yield extension.unload();
});
// Test that an error is thrown when providing invalid icon sizes
add_task(function *testInvalidIconSizes() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {},
"page_action": {},
},
background: function () {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
var tabId = tabs[0].id;
for (var api of ["pageAction", "browserAction"]) {
// helper function to run setIcon and check if it fails
let assertSetIconThrows = function(detail, error, message) {
try {
detail.tabId = tabId;
browser[api].setIcon(detail);
browser.test.fail("Expected an error on invalid icon size.");
browser.test.notifyFail("setIcon with invalid icon size");
return;
} catch (e) {
browser.test.succeed("setIcon with invalid icon size");
}
}
// test invalid icon size inputs
for (var type of ["path", "imageData"]) {
assertSetIconThrows({ [type]: { "abcdef": "test.png" } });
assertSetIconThrows({ [type]: { "48px": "test.png" } });
assertSetIconThrows({ [type]: { "20.5": "test.png" } });
assertSetIconThrows({ [type]: { "5.0": "test.png" } });
assertSetIconThrows({ [type]: { "-300": "test.png" } });
assertSetIconThrows({ [type]: {
"abc": "test.png",
"5": "test.png"
}});
}
assertSetIconThrows({ imageData: { "abcdef": "test.png" }, path: {"5": "test.png"} });
assertSetIconThrows({ path: { "abcdef": "test.png" }, imageData: {"5": "test.png"} });
}
browser.test.notifyPass("setIcon with invalid icon size");
});
}
});
yield Promise.all([extension.startup(), extension.awaitFinish("setIcon with invalid icon size")]);
yield extension.unload();
});
// Test that default icon details in the manifest.json file are handled
// correctly.
add_task(function *testDefaultDetails() {
@ -294,7 +373,7 @@ add_task(function* testSecureURLsDenied() {
yield extension.startup();
yield extension.awaitFinish();
yield extension.awaitFinish("setIcon security tests");
yield extension.unload();
@ -304,12 +383,12 @@ add_task(function* testSecureURLsDenied() {
"javascript:true"];
let matchURLForbidden = url => ({
message: new RegExp(`Loading extension.*Access to.*'${url}' denied`),
message: new RegExp(`Loading extension.*Invalid icon data: NS_ERROR_DOM_BAD_URI`),
});
// Because the underlying method throws an error on invalid data,
// only the first invalid URL of each component will be logged.
let messages = [matchURLForbidden(urls[0]),
matchURLForbidden(urls[1]),
matchURLForbidden(urls[0]),
matchURLForbidden(urls[1])];
let waitForConsole = new Promise(resolve => {
@ -330,8 +409,8 @@ add_task(function* testSecureURLsDenied() {
},
"page_action": {
"default_icon": {
"19": urls[0],
"38": urls[1],
"19": urls[1],
"38": urls[0],
},
},
},

View File

@ -440,7 +440,16 @@ loop.panel = (function(_, mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
this.closeWindow();
// Open url if needed.
loop.request("getSelectedTabMetadata").then(function(metadata) {
var contextURL = this.props.room.decryptedContext.urls &&
this.props.room.decryptedContext.urls[0].location;
if (contextURL && metadata.url !== contextURL) {
loop.request("OpenURL", contextURL);
}
this.closeWindow();
}.bind(this));
},
handleClick: function(e) {

View File

@ -440,7 +440,16 @@ loop.panel = (function(_, mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
roomToken: this.props.room.roomToken
}));
this.closeWindow();
// Open url if needed.
loop.request("getSelectedTabMetadata").then(function(metadata) {
var contextURL = this.props.room.decryptedContext.urls &&
this.props.room.decryptedContext.urls[0].location;
if (contextURL && metadata.url !== contextURL) {
loop.request("OpenURL", contextURL);
}
this.closeWindow();
}.bind(this));
},
handleClick: function(e) {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1 @@
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>FX_Hello-glyph-pause-12x12</title><desc>Created with Sketch.</desc><path d="M5 .5v11c0 .136-.05.253-.149.351-.099.099-.215.149-.351.149h-4c-.136 0-.253-.05-.352-.149-.099-.098-.148-.216-.148-.351v-11c0-.136.049-.253.148-.352.099-.098.217-.148.352-.148h4c.136 0 .252.05.351.148.099.099.149.217.149.352zm7 0v11c0 .136-.05.253-.149.351-.099.099-.216.149-.351.149h-4c-.136 0-.253-.05-.352-.149-.099-.098-.148-.216-.148-.351v-11c0-.136.049-.253.148-.352.099-.098.216-.148.352-.148h4c.136 0 .252.05.351.148.099.099.149.217.149.352z" sketch:type="MSShapeGroup" fill="#333"/></svg>

After

Width:  |  Height:  |  Size: 718 B

View File

@ -0,0 +1 @@
<svg width="11" height="12" viewBox="0 0 11 12" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>FX_Hello-glyph-play-12x12</title><desc>Created with Sketch.</desc><path d="M10.695 6.24l-10.263 5.704c-.118.066-.22.074-.305.023-.085-.052-.127-.144-.127-.278v-11.377c0-.134.043-.228.127-.278.085-.052.186-.044.305.022l10.263 5.704c.118.066.178.147.178.239 0 .094-.06.174-.178.241z" sketch:type="MSShapeGroup" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@ -0,0 +1 @@
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>FX_Hello-glyph-stop-12x12</title><desc>Created with Sketch.</desc><path d="M12 .5v11c0 .136-.05.253-.149.351-.099.099-.216.149-.351.149h-11c-.136 0-.253-.05-.352-.149-.099-.098-.148-.216-.148-.351v-11c0-.136.049-.253.148-.352.099-.098.217-.148.352-.148h11c.136 0 .252.05.351.148.099.099.149.217.149.352z" sketch:type="MSShapeGroup" fill="#D92215"/></svg>

After

Width:  |  Height:  |  Size: 499 B

View File

@ -89,6 +89,9 @@ browser.jar:
content/browser/loop/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
content/browser/loop/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
content/browser/loop/shared/img/pause-12x12.svg (content/shared/img/pause-12x12.svg)
content/browser/loop/shared/img/play-12x12.svg (content/shared/img/play-12x12.svg)
content/browser/loop/shared/img/stop-12x12.svg (content/shared/img/stop-12x12.svg)
# Shared scripts
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)

View File

@ -875,10 +875,11 @@ var MozLoopServiceInternal = {
*/
hangupAllChatWindows() {
let isLoopURL = ({ src }) => /^about:loopconversation#/.test(src);
[...Chat.chatboxes].filter(isLoopURL).forEach(chatbox => {
let loopChatWindows = [...Chat.chatboxes].filter(isLoopURL);
for (let chatbox of loopChatWindows) {
let window = chatbox.content.contentWindow;
window.dispatchEvent(new window.CustomEvent("LoopHangupNow"));
});
}
},
/**

View File

@ -82,7 +82,10 @@ describe("loop.panel", function() {
roomToken: "QzBbvGmIZWU",
roomUrl: "http://sample/QzBbvGmIZWU",
decryptedContext: {
roomName: roomName
roomName: roomName,
urls: [{
location: "http://testurl.com"
}]
},
maxSize: 2,
participants: [{
@ -665,12 +668,23 @@ describe("loop.panel", function() {
});
describe("Copy button", function() {
var roomEntry;
var roomEntry, openURLStub;
beforeEach(function() {
// Stub to prevent warnings where no stores are set up to handle the
// actions we are testing.
sandbox.stub(dispatcher, "dispatch");
openURLStub = sinon.stub();
LoopMochaUtils.stubLoopRequest({
GetSelectedTabMetadata: function() {
return {
url: "http://invalid.com",
description: "fakeSite"
};
},
OpenURL: openURLStub
});
roomEntry = mountRoomEntry({
deleteRoom: sandbox.stub(),
@ -717,6 +731,32 @@ describe("loop.panel", function() {
sinon.assert.notCalled(dispatcher.dispatch);
});
it("should open a new tab with the room context if it is not the same as the currently open tab", function() {
TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode());
sinon.assert.calledOnce(openURLStub);
sinon.assert.calledWithExactly(openURLStub, "http://testurl.com");
});
it("should not open a new tab if the context is the same as the currently open tab", function() {
LoopMochaUtils.stubLoopRequest({
GetSelectedTabMetadata: function() {
return {
url: "http://testurl.com",
description: "fakeSite"
};
}
});
roomEntry = mountRoomEntry({
deleteRoom: sandbox.stub(),
isOpenedRoom: false,
room: new loop.store.Room(roomData)
});
TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode());
sinon.assert.notCalled(openURLStub);
});
});
});

View File

@ -156,11 +156,6 @@ add_task(function* test_infoBar() {
let button = bar.querySelector(".notification-button");
Assert.ok(button, "There should be a button present");
Assert.strictEqual(button.type, "menu-button", "We're expecting a menu-button");
Assert.strictEqual(button.getAttribute("anchor"), "dropmarker",
"The popup should be opening anchored to the dropmarker");
Assert.strictEqual(button.getElementsByTagNameNS(kNSXUL, "menupopup").length, 1,
"There should be a popup attached to the button");
};
testBarProps();
@ -175,11 +170,8 @@ add_task(function* test_infoBar() {
testBarProps();
// Test hiding the infoBar.
getInfoBar().querySelector(".notification-button")
.getElementsByTagNameNS(kNSXUL, "menuitem")[0].click();
getInfoBar().querySelector(".notification-button-default").click();
Assert.equal(getInfoBar(), null, "The notification should be hidden now");
Assert.strictEqual(Services.prefs.getBoolPref(kPrefBrowserSharingInfoBar), false,
"The pref should be set to false when the menu item is clicked");
gBrowser.selectedIndex = Array.indexOf(gBrowser.tabs, createdTabs[1]);

View File

@ -64,6 +64,8 @@ var LoopMochaUtils = (function(global, _) {
throw result;
};
this.catch = function() {};
asyncFn(this.resolve.bind(this), this.reject.bind(this));
}

View File

@ -7,6 +7,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@ -15,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
"Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" +
"microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge";
const kEdgeReadingListPath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
function EdgeTypedURLMigrator() {
}
@ -79,6 +81,112 @@ EdgeTypedURLMigrator.prototype = {
},
}
function EdgeReadingListMigrator() {
}
EdgeReadingListMigrator.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
get exists() {
return !!MSMigrationUtils.getEdgeLocalDataFolder();
},
migrate(callback) {
this._migrateReadingList(PlacesUtils.bookmarks.menuGuid).then(
() => callback(true),
ex => {
Cu.reportError(ex);
callback(false);
}
);
},
_migrateReadingList: Task.async(function*(parentGuid) {
let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder();
if (!edgeDir) {
return;
}
this._readingListExtractor = Cc["@mozilla.org/profile/migrator/edgereadinglistextractor;1"].
createInstance(Ci.nsIEdgeReadingListExtractor);
edgeDir.appendRelativePath(kEdgeReadingListPath);
let errorProduced = null;
if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
let expectedDir = edgeDir.clone();
expectedDir.appendRelativePath("nouser1\\120712-0049");
if (expectedDir.exists() && expectedDir.isReadable() && expectedDir.isDirectory()) {
yield this._migrateReadingListDB(expectedDir, parentGuid).catch(ex => {
if (!errorProduced)
errorProduced = ex;
});
} else {
let getSubdirs = someDir => {
let subdirs = someDir.directoryEntries;
let rv = [];
while (subdirs.hasMoreElements()) {
let subdir = subdirs.getNext().QueryInterface(Ci.nsIFile);
if (subdir.isDirectory() && subdir.isReadable()) {
rv.push(subdir);
}
}
return rv;
};
let dirs = getSubdirs(edgeDir).map(getSubdirs);
for (let dir of dirs) {
yield this._migrateReadingListDB(dir, parentGuid).catch(ex => {
if (!errorProduced)
errorProduced = ex;
});
}
}
}
if (errorProduced) {
throw errorProduced;
}
}),
_migrateReadingListDB: Task.async(function*(dbFile, parentGuid) {
dbFile.appendRelativePath("DBStore\\spartan.edb");
if (!dbFile.exists() || !dbFile.isReadable() || !dbFile.isFile()) {
return;
}
let readingListItems;
try {
readingListItems = this._readingListExtractor.extract(dbFile.path);
} catch (ex) {
Cu.reportError("Failed to extract Edge reading list information from " +
"the database at " + dbFile.path + " due to the following error: " + ex);
// Deliberately make this fail so we expose failure in the UI:
throw ex;
return;
}
if (!readingListItems.length) {
return;
}
let destFolderGuid = yield this._ensureReadingListFolder(parentGuid);
for (let i = 0; i < readingListItems.length; i++) {
let readingListItem = readingListItems.queryElementAt(i, Ci.nsIPropertyBag2);
let url = readingListItem.get("uri");
let title = readingListItem.get("title");
let time = readingListItem.get("time");
// time is a PRTime, which is microseconds (since unix epoch), or null.
// We need milliseconds for the date constructor, so divide by 1000:
let dateAdded = time ? new Date(time / 1000) : new Date();
yield PlacesUtils.bookmarks.insert({
parentGuid: destFolderGuid, url: url, title, dateAdded
});
}
}),
_ensureReadingListFolder: Task.async(function*(parentGuid) {
if (!this.__readingListFolderGuid) {
let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
this.__readingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid;
}
return this.__readingListFolderGuid;
}),
};
function EdgeProfileMigrator() {
}
@ -89,6 +197,7 @@ EdgeProfileMigrator.prototype.getResources = function() {
MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
new EdgeTypedURLMigrator(),
new EdgeReadingListMigrator(),
];
let windowsVaultFormPasswordsMigrator =
MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();

View File

@ -26,7 +26,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
const FREE_CLOSE_FAILED = 0;
const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499,
0x4B1087A8,
@ -376,10 +375,6 @@ Bookmarks.prototype = {
yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
}
yield this._migrateFolder(this._favoritesFolder, folderGuid);
if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
yield this._migrateEdgeReadingList(PlacesUtils.bookmarks.menuGuid);
}
}.bind(this)).then(() => aCallback(true),
e => { Cu.reportError(e); aCallback(false) });
},
@ -390,6 +385,7 @@ Bookmarks.prototype = {
// for IE, and in a similar location for Edge.
// Until we support it, bookmarks are imported in alphabetical order.
let entries = aSourceFolder.directoryEntries;
let succeeded = true;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
try {
@ -439,82 +435,14 @@ Bookmarks.prototype = {
}
} catch (ex) {
Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
succeeded = false;
}
}
}),
_migrateEdgeReadingList: Task.async(function*(parentGuid) {
let edgeDir = getEdgeLocalDataFolder();
if (!edgeDir) {
return;
}
this._readingListExtractor = Cc["@mozilla.org/profile/migrator/edgereadinglistextractor;1"].
createInstance(Ci.nsIEdgeReadingListExtractor);
edgeDir.appendRelativePath(EDGE_READINGLIST);
if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
let expectedDir = edgeDir.clone();
expectedDir.appendRelativePath("nouser1\\120712-0049");
if (expectedDir.exists() && expectedDir.isReadable() && expectedDir.isDirectory()) {
yield this._migrateEdgeReadingListDB(expectedDir, parentGuid);
} else {
let getSubdirs = someDir => {
let subdirs = someDir.directoryEntries;
let rv = [];
while (subdirs.hasMoreElements()) {
let subdir = subdirs.getNext().QueryInterface(Ci.nsIFile);
if (subdir.isDirectory() && subdir.isReadable()) {
rv.push(subdir);
}
}
return rv;
};
let dirs = getSubdirs(edgeDir).map(getSubdirs);
for (let dir of dirs) {
yield this._migrateEdgeReadingListDB(dir, parentGuid);
}
}
}
}),
_migrateEdgeReadingListDB: Task.async(function*(dbFile, parentGuid) {
dbFile.appendRelativePath("DBStore\\spartan.edb");
if (!dbFile.exists() || !dbFile.isReadable() || !dbFile.isFile()) {
return;
}
let readingListItems;
try {
readingListItems = this._readingListExtractor.extract(dbFile.path);
} catch (ex) {
Cu.reportError("Failed to extract Edge reading list information from " +
"the database at " + dbPath + " due to the following error: " + ex);
return;
}
if (!readingListItems.length) {
return;
}
let destFolderGuid = yield this._ensureEdgeReadingListFolder(parentGuid);
for (let i = 0; i < readingListItems.length; i++) {
let readingListItem = readingListItems.queryElementAt(i, Ci.nsIPropertyBag2);
let url = readingListItem.get("uri");
let title = readingListItem.get("title");
let time = readingListItem.get("time");
// time is a PRTime, which is microseconds (since unix epoch), or null.
// We need milliseconds for the date constructor, so divide by 1000:
let dateAdded = time ? new Date(time / 1000) : new Date();
yield PlacesUtils.bookmarks.insert({
parentGuid: destFolderGuid, url: url, title, dateAdded
});
if (!succeeded) {
throw new Error("Failed to import all bookmarks correctly.");
}
}),
_ensureEdgeReadingListFolder: Task.async(function*(parentGuid) {
if (!this.__edgeReadingListFolderGuid) {
let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
this.__edgeReadingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid;
}
return this.__edgeReadingListFolderGuid;
}),
};
function Cookies(migrationType) {
@ -948,4 +876,5 @@ var MSMigrationUtils = {
return new WindowsVaultFormPasswords();
},
getTypedURLs,
getEdgeLocalDataFolder,
};

View File

@ -197,7 +197,13 @@ nsEdgeReadingListExtractor::ConvertJETError(const JET_ERR &aError)
return NS_ERROR_FILE_INVALID_PATH;
case JET_errFileNotFound:
return NS_ERROR_FILE_NOT_FOUND;
case JET_errDatabaseDirtyShutdown:
return NS_ERROR_FILE_CORRUPTED;
default:
nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
wchar_t* msg = new wchar_t[80];
swprintf(msg, 80, MOZ_UTF16("Unexpected JET error from ESE database: %ld"), aError);
consoleService->LogStringMessage(msg);
return NS_ERROR_FAILURE;
}
}

View File

@ -8,14 +8,20 @@ about=Test distribution file
[BookmarksToolbar]
item.1.title=Toolbar Link Before
item.1.link=http://mozilla.com/
item.1.link=https://example.org/toolbar/before/
item.1.keyword=e:t:b
item.1.icon=https://example.org/favicon.png
item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
item.2.type=default
item.3.title=Toolbar Link After
item.3.link=http://mozilla.com/
item.3.link=https://example.org/toolbar/after/
item.3.keyword=e:t:a
[BookmarksMenu]
item.1.title=Menu Link Before
item.1.link=http://mozilla.com/
item.1.link=https://example.org/menu/before/
item.1.icon=https://example.org/favicon.png
item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
item.2.type=default
item.3.title=Menu Link After
item.3.link=http://mozilla.com/
item.3.link=https://example.org/menu/after/

View File

@ -119,3 +119,34 @@ function rebuildSmartBookmarks() {
Services.console.unregisterListener(consoleListener);
});
}
const SINGLE_TRY_TIMEOUT = 100;
const NUMBER_OF_TRIES = 30;
/**
* Similar to waitForConditionPromise, but poll for an asynchronous value
* every SINGLE_TRY_TIMEOUT ms, for no more than tryCount times.
*
* @param promiseFn
* A function to generate a promise, which resolves to the expected
* asynchronous value.
* @param timeoutMsg
* The reason to reject the returned promise with.
* @param [optional] tryCount
* Maximum times to try before rejecting the returned promise with
* timeoutMsg, defaults to NUMBER_OF_TRIES.
* @return {Promise}
* @resolves to the asynchronous value being polled.
* @rejects if the asynchronous value is not available after tryCount attempts.
*/
var waitForResolvedPromise = Task.async(function* (promiseFn, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
let tries = 0;
do {
try {
let value = yield promiseFn();
return value;
} catch (ex) {}
yield new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve));
} while (++tries <= tryCount);
throw(timeoutMsg);
});

View File

@ -76,6 +76,16 @@ add_task(function* () {
});
Assert.equal(menuItem.title, "Menu Link After");
// Check no favicon or keyword exists for this bookmark
yield Assert.rejects(waitForResolvedPromise(() => {
return PlacesUtils.promiseFaviconData(menuItem.url.href);
}, "Favicon not found", 10), /Favicon\snot\sfound/, "Favicon not found");
let keywordItem = yield PlacesUtils.keywords.fetch({
url: menuItem.url.href
});
Assert.strictEqual(keywordItem, null);
// Check the custom bookmarks exist on toolbar.
let toolbarItem = yield PlacesUtils.bookmarks.fetch({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
@ -83,6 +93,24 @@ add_task(function* () {
});
Assert.equal(toolbarItem.title, "Toolbar Link Before");
// Check the custom favicon and keyword exist for this bookmark
let faviconItem = yield waitForResolvedPromise(() => {
return PlacesUtils.promiseFaviconData(toolbarItem.url.href);
}, "Favicon not found", 10);
Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png");
Assert.greater(faviconItem.dataLen, 0);
Assert.equal(faviconItem.mimeType, "image/png");
let base64Icon = "data:image/png;base64," +
base64EncodeString(String.fromCharCode.apply(String, faviconItem.data));
Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec);
keywordItem = yield PlacesUtils.keywords.fetch({
url: toolbarItem.url.href
});
Assert.notStrictEqual(keywordItem, null);
Assert.equal(keywordItem.keyword, "e:t:b");
toolbarItem = yield PlacesUtils.bookmarks.fetch({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
index: 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR

View File

@ -1250,6 +1250,19 @@ var SessionStoreInternal = {
// we don't want to save the busy state
delete winData.busy;
// When closing windows one after the other until Firefox quits, we
// will move those closed in series back to the "open windows" bucket
// before writing to disk. If however there is only a single window
// with tabs we deem not worth saving then we might end up with a
// random closed or even a pop-up window re-opened. To prevent that
// we explicitly allow saving an "empty" window state.
let isLastWindow =
Object.keys(this._windows).length == 1 &&
!this._closedWindows.some(win => win._shouldRestore || false);
// clear this window from the list, since it has definitely been closed.
delete this._windows[aWindow.__SSi];
// Now we have to figure out if this window is worth saving in the _closedWindows
// Object.
//
@ -1265,7 +1278,7 @@ var SessionStoreInternal = {
if (!winData.isPrivate) {
// Remove any open private tabs the window may contain.
PrivacyFilter.filterPrivateTabs(winData);
this.maybeSaveClosedWindow(winData);
this.maybeSaveClosedWindow(winData, isLastWindow);
}
// The tabbrowser binding will go away once the window is closed,
@ -1292,11 +1305,9 @@ var SessionStoreInternal = {
// It's possible that a tab switched its privacy state at some point
// before our flush, so we need to filter again.
PrivacyFilter.filterPrivateTabs(winData);
this.maybeSaveClosedWindow(winData);
this.maybeSaveClosedWindow(winData, isLastWindow);
}
// clear this window from the list
delete this._windows[aWindow.__SSi];
// Update the tabs data now that we've got the most
// recent information.
this.cleanUpWindow(aWindow, winData);
@ -1313,7 +1324,6 @@ var SessionStoreInternal = {
}
},
/**
* Clean up the message listeners on a window that has finally
* gone away. Call this once you're sure you don't want to hear
@ -1345,22 +1355,19 @@ var SessionStoreInternal = {
*
* @param winData
* The data for the closed window that we might save.
* @param isLastWindow
* Whether or not the window being closed is the last
* browser window. Callers of this function should pass
* in the value of SessionStoreInternal.atLastWindow for
* this argument, and pass in the same value if they happen
* to call this method again asynchronously (for example, after
* a window flush).
*/
maybeSaveClosedWindow(winData) {
maybeSaveClosedWindow(winData, isLastWindow) {
if (RunState.isRunning) {
// Determine whether the window has any tabs worth saving.
let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
// When closing windows one after the other until Firefox quits, we
// will move those closed in series back to the "open windows" bucket
// before writing to disk. If however there is only a single window
// with tabs we deem not worth saving then we might end up with a
// random closed or even a pop-up window re-opened. To prevent that
// we explicitly allow saving an "empty" window state.
let isLastWindow =
Object.keys(this._windows).length == 1 &&
!this._closedWindows.some(win => win._shouldRestore || false);
// Note that we might already have this window stored in
// _closedWindows from a previous call to this function.
let winIndex = this._closedWindows.indexOf(winData);

View File

@ -217,3 +217,4 @@ skip-if = os == "mac"
[browser_send_async_message_oom.js]
[browser_multiple_navigateAndRestore.js]
run-if = e10s
[browser_async_window_flushing.js]

View File

@ -0,0 +1,38 @@
/**
* Tests that when we close a window, it is immediately removed from the
* _windows array.
*/
add_task(function* test_synchronously_remove_window_state() {
// Depending on previous tests, we might already have some closed
// windows stored. We'll use its length to determine whether or not
// the window was added or not.
let state = JSON.parse(ss.getBrowserState());
ok(state, "Make sure we can get the state");
let initialWindows = state.windows.length;
// Open a new window and send the first tab somewhere
// interesting.
let newWin = yield BrowserTestUtils.openNewBrowserWindow();
let browser = newWin.gBrowser.selectedBrowser;
browser.loadURI("http://example.com");
yield BrowserTestUtils.browserLoaded(browser);
yield TabStateFlusher.flush(browser);
state = JSON.parse(ss.getBrowserState());
is(state.windows.length, initialWindows + 1,
"The new window to be in the state");
// Now close the window, and make sure that the window was removed
// from the windows list from the SessionState. We're specifically
// testing the case where the window is _not_ removed in between
// the close-initiated flush request and the flush response.
let windowClosed = BrowserTestUtils.windowClosed(newWin);
newWin.close();
state = JSON.parse(ss.getBrowserState());
is(state.windows.length, initialWindows,
"The new window should have been removed from the state");
// Wait for our window to go away
yield windowClosed;
});

View File

@ -198,11 +198,14 @@ room_name_untitled_page=Untitled Page
# Infobar strings
infobar_screenshare_browser_message=Users in your conversation will now be able to see the contents of any tab you click on.
infobar_button_gotit_label=Got it!
infobar_button_gotit_accesskey=G
infobar_menuitem_dontshowagain_label=Don't show this again
infobar_menuitem_dontshowagain_accesskey=D
infobar_screenshare_browser_message=You are sharing your tabs. Any tab you click on can be seen by your friends
infobar_screenshare_paused_browser_message=Tab sharing is paused
infobar_button_pause_label=Pause
infobar_button_resume_label=Resume
infobar_button_stop_label=Stop
infobar_button_pause_accesskey=P
infobar_button_stop_accesskey=S
infobar_button_resume_accesskey=R
# Context in conversation strings

View File

@ -1118,16 +1118,6 @@ notification[value="translation"] menulist > .menulist-dropmarker {
display: block;
}
/* Loop/ Hello browser styles */
notification[value="loop-sharing-notification"] .button-menubutton-button {
min-width: 0;
}
notification[value="loop-sharing-notification"] .messageImage {
list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16.png);
}
#treecolAutoCompleteImage {
max-width : 36px;
}

View File

@ -3212,24 +3212,37 @@ menulist.translate-infobar-element > .menulist-dropmarker {
}
/* Loop/ Hello browser styles */
notification[value="loop-sharing-notification"] {
background: #00a9dc;
padding: 0;
border: 0;
}
notification[value="loop-sharing-notification"].paused {
background: #ebebeb;
}
notification[value="loop-sharing-notification"] .notification-button {
padding: 1px 5px;
background: #fff;
border-radius: 0;
}
notification[value="loop-sharing-notification"] .button-menubutton-button {
-moz-appearance: none;
min-width: 0;
margin: 0;
notification[value="loop-sharing-notification"].paused .notification-button {
background: #57bd35;
}
notification[value="loop-sharing-notification"] .messageImage {
list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-menubar.png);
notification[value="loop-sharing-notification"].paused .notification-button:hover {
background: #39a017;
}
@media (min-resolution: 2dppx) {
notification[value="loop-sharing-notification"] .messageImage {
list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-menubar@2x.png);
}
notification[value="loop-sharing-notification"] .notification-button:hover,
notification[value="loop-sharing-notification"].paused .notification-button-default:hover {
background: #ebebeb;
}
notification[value="loop-sharing-notification"] .notification-button-default,
notification[value="loop-sharing-notification"].paused .notification-button-default {
background: #fff;
}
.popup-notification-body[popupid="addon-progress"],

View File

@ -445,3 +445,83 @@
}
%endif
}
/* Loop notification */
notification[value="loop-sharing-notification"] {
-moz-appearance: none;
height: 40px;
background-color: #00a9dc;
box-shadow: 0 40px 1px rgba(0,0,0,.5) inset;
}
notification[value="loop-sharing-notification"].paused {
background-color: #ebebeb;
}
notification[value="loop-sharing-notification"] .notification-inner {
color: #fff;
padding: 0;
}
notification[value="loop-sharing-notification"].paused .notification-inner {
color: #00a9dc;
}
notification[value="loop-sharing-notification"] .notification-button {
-moz-appearance: none;
background-color: #fff;
border: 0;
border-right: solid 1px #ebebeb;
width: 100px;
height: 40px;
margin: 0;
list-style-image: url(chrome://browser/content/loop/shared/img/pause-12x12.svg);
box-shadow: 0 40px 1px rgba(0,0,0,.5) inset;
text-shadow: none;
}
notification[value="loop-sharing-notification"] .notification-button:-moz-locale-dir(rtl) {
border-right: 0;
border-left: solid 1px #ebebeb;
}
notification[value="loop-sharing-notification"].paused .notification-button {
background-color: #57bd35;
color: #fff;
list-style-image: url(chrome://browser/content/loop/shared/img/play-12x12.svg);
}
notification[value="loop-sharing-notification"].paused .notification-button:hover {
background-color: #39a017;
}
notification[value="loop-sharing-notification"] .notification-button:hover,
notification[value="loop-sharing-notification"].paused .notification-button-default:hover {
background-color: #ebebeb;
}
notification[value="loop-sharing-notification"] .notification-button-default,
notification[value="loop-sharing-notification"].paused .notification-button-default {
color: #d92215;
background-color: #fff;
border-right: 0;
list-style-image: url(chrome://browser/content/loop/shared/img/stop-12x12.svg);
}
notification[value="loop-sharing-notification"] .notification-button .button-icon {
display: block;
-moz-margin-end: 6px;
}
notification[value="loop-sharing-notification"] .button-menubutton-button {
min-width: 0;
}
notification[value="loop-sharing-notification"] .messageImage {
list-style-image: url(chrome://browser/content/loop/shared/img/icons-16x16.svg#loop-icon-white);
margin-inline-start: 14px;
}
notification[value="loop-sharing-notification"].paused .messageImage {
list-style-image: url(chrome://browser/content/loop/shared/img/icons-16x16.svg#loop-icon-still);
}

View File

@ -2316,19 +2316,6 @@ notification[value="translation"] {
-moz-appearance: none;
}
/* Loop/ Hello browser styles */
notification[value="loop-sharing-notification"] .button-menubutton-button {
-moz-appearance: none;
min-width: 0;
border: 0;
margin: 0;
}
notification[value="loop-sharing-notification"] .messageImage {
list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16.png);
}
/* Bookmarks roots menu-items */
#subscribeToPageMenuitem:not([disabled]),
#subscribeToPageMenupopup,

View File

@ -66,9 +66,9 @@ if test "$COMPILER_WRAPPER" != "no"; then
_SUBDIR_CXX="$CXX"
ac_cv_prog_CC="$CC"
ac_cv_prog_CXX="$CXX"
MOZ_USING_COMPILER_WRAPPER=1
;;
esac
MOZ_USING_COMPILER_WRAPPER=1
fi
AC_SUBST(MOZ_USING_COMPILER_WRAPPER)

View File

@ -6987,7 +6987,7 @@ AC_SUBST(MOZ_FRAMEPTR_FLAGS)
AC_SUBST(MOZ_OPTIMIZE_FLAGS)
AC_SUBST(MOZ_OPTIMIZE_RUSTFLAGS)
AC_SUBST(MOZ_OPTIMIZE_LDFLAGS)
AC_SUBST(MOZ_ALLOW_HEAP_EXECUTE_FLAGS)
AC_SUBST_LIST(MOZ_ALLOW_HEAP_EXECUTE_FLAGS)
AC_SUBST(MOZ_PGO)
AC_SUBST(MOZ_PGO_OPTIMIZE_FLAGS)

View File

@ -12,7 +12,8 @@ h2, h3, h4 {
}
button {
width: 100px;
padding-left: 20px;
padding-right: 20px;
}
#body {
@ -80,3 +81,12 @@ label {
.target-details {
flex: 1;
}
.addon-controls {
display: flex;
flex-direction: row;
}
.addon-options {
flex: 1;
}

View File

@ -8,6 +8,7 @@
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { loader } = Components.utils.import(
"resource://devtools/shared/Loader.jsm", {});
@ -23,7 +24,15 @@ loader.lazyRequireGetter(this, "WorkersComponent",
"devtools/client/aboutdebugging/components/workers", true);
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
var AboutDebugging = {
_prefListeners: [],
_categories: null,
get categories() {
// If needed, initialize the list of available categories.
@ -72,14 +81,19 @@ var AboutDebugging = {
let updatePref = () => {
Services.prefs.setBoolPref(pref, element.checked);
};
element.addEventListener("change", updatePref, false);
let updateCheckbox = () => {
element.checked = Services.prefs.getBoolPref(pref);
};
element.addEventListener("change", updatePref, false);
Services.prefs.addObserver(pref, updateCheckbox, false);
this._prefListeners.push([pref, updateCheckbox]);
updateCheckbox();
});
// Link buttons to their associated actions.
let loadAddonButton = document.getElementById("load-addon-from-file");
loadAddonButton.addEventListener("click", this.loadAddonFromFile);
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
@ -95,10 +109,41 @@ var AboutDebugging = {
});
},
loadAddonFromFile() {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window,
Strings.GetStringFromName("selectAddonFromFile"),
Ci.nsIFilePicker.modeOpen);
let res = fp.show();
if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
return;
}
let file = fp.file;
// AddonManager.installTemporaryAddon accepts either
// addon directory or final xpi file.
if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
file = file.parent;
}
try {
AddonManager.installTemporaryAddon(file);
} catch(e) {
alert("Error while installing the addon:\n" + e.message + "\n");
throw e;
}
},
destroy() {
let telemetry = this._telemetry;
telemetry.toolClosed("aboutdebugging");
telemetry.destroy();
this._prefListeners.forEach(([pref, listener]) => {
Services.prefs.removeObserver(pref, listener);
});
this._prefListeners = [];
React.unmountComponentAtNode(document.querySelector("#addons"));
React.unmountComponentAtNode(document.querySelector("#workers"));
},
};

View File

@ -34,8 +34,13 @@
<div class="header">
<h1 class="header-name">&aboutDebugging.addons;</h1>
</div>
<input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/>
<label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label>
<div class="addon-controls">
<div class="addon-options">
<input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/>
<label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label>
</div>
<button id="load-addon-from-file">&aboutDebugging.loadTemporaryAddon;</button>
</div>
<div id="addons"></div>
</div>
<div id="tab-workers" class="tab">

View File

@ -3,4 +3,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/.
DIST_INSTALL = False
DIRS += [
'components',
]
BROWSER_CHROME_MANIFESTS += [
'test/browser.ini'
]

View File

@ -0,0 +1,7 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function startup() {
Services.obs.notifyObservers(null, "test-devtools", null);
}
function shutdown() {}
function install() {}
function uninstall() {}

View File

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<!--
# 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/.
-->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest"
em:id="test-devtools@mozilla.org"
em:name="test-devtools"
em:version="1.0"
em:type="2"
em:creator="Mozilla">
<em:bootstrap>true</em:bootstrap>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>44.0a1</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1,9 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
head.js
addons/unpacked/bootstrap.js
addons/unpacked/install.rdf
[browser_addons_install.js]

View File

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
add_task(function *() {
let { tab, document } = yield openAboutDebugging("addons");
// Mock the file picker to select a test addon
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(null);
let file = get_supports_file("addons/unpacked/install.rdf");
MockFilePicker.returnFiles = [file.file];
// Wait for a message sent by the addon's bootstrap.js file
let promise = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, "test-devtools", false);
ok(true, "Addon installed and running its bootstrap.js file");
done();
}, "test-devtools", false);
});
// Trigger the file picker by clicking on the button
document.getElementById("load-addon-from-file").click();
// Wait for the addon execution
yield promise;
// Check that the addon appears in the UI
let names = [...document.querySelectorAll("#addons .target-name")];
names = names.map(element => element.textContent);
ok(names.includes(ADDON_NAME), "The addon name appears in the list of addons: " + names);
// Now uninstall this addon
yield new Promise(done => {
AddonManager.getAddonByID(ADDON_ID, addon => {
let listener = {
onUninstalled: function(aUninstalledAddon) {
if (aUninstalledAddon != addon) {
return;
}
AddonManager.removeAddonListener(listener);
done();
}
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
});
// Ensure that the UI removes the addon from the list
names = [...document.querySelectorAll("#addons .target-name")];
names = names.map(element => element.textContent);
ok(!names.includes(ADDON_NAME), "After uninstall, the addon name disappears from the list of addons: " + names);
yield closeAboutDebugging(tab);
});

View File

@ -0,0 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
DevToolsUtils.testing = true;
const CHROME_ROOT = gTestPath.substr(0, gTestPath.lastIndexOf("/") + 1);
registerCleanupFunction(() => {
DevToolsUtils.testing = false;
});
function openAboutDebugging() {
info("opening about:debugging");
return addTab("about:debugging").then(tab => {
let browser = tab.linkedBrowser;
return {
tab,
document: browser.contentDocument,
window: browser.contentWindow
};
});
}
function closeAboutDebugging(tab) {
info("Closing about:debugging");
return removeTab(tab);
}
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
return new Promise(done => {
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
done(tab);
}, true);
});
}
function removeTab(aTab, aWindow) {
info("Removing tab.");
return new Promise(done => {
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
tabContainer.removeEventListener("TabClose", onClose, false);
info("Tab removed and finished closing.");
done();
}, false);
targetBrowser.removeTab(aTab);
});
}
function get_supports_file(path) {
let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
getService(Ci.nsIChromeRegistry);
let fileurl = cr.convertChromeURL(Services.io.newURI(CHROME_ROOT + path, null, null));
return fileurl.QueryInterface(Ci.nsIFileURL);
}

View File

@ -565,6 +565,7 @@ function AnimationsTimeline(inspector) {
this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this);
this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this);
this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this);
this.onAnimationSelected = this.onAnimationSelected.bind(this);
EventEmitter.decorate(this);
}
@ -633,14 +634,17 @@ AnimationsTimeline.prototype = {
this.win = null;
this.inspector = null;
},
destroyTargetNodes: function() {
for (let targetNode of this.targetNodes) {
targetNode.destroy();
}
this.targetNodes = [];
},
destroyTimeBlocks: function() {
for (let timeBlock of this.timeBlocks) {
timeBlock.off("selected", this.onAnimationSelected);
timeBlock.destroy();
}
this.timeBlocks = [];
@ -656,6 +660,24 @@ AnimationsTimeline.prototype = {
this.animationsEl.innerHTML = "";
},
onAnimationSelected: function(e, animation) {
// Unselect the previously selected animation if any.
[...this.rootWrapperEl.querySelectorAll(".animation.selected")].forEach(el => {
el.classList.remove("selected");
});
// Select the new animation.
let index = this.animations.indexOf(animation);
if (index === -1) {
return;
}
this.rootWrapperEl.querySelectorAll(".animation")[index]
.classList.toggle("selected");
// Relay the event to the parent component.
this.emit("selected", animation);
},
onScrubberMouseDown: function(e) {
this.moveScrubberTo(e.pageX);
this.win.addEventListener("mouseup", this.onScrubberMouseUp);
@ -764,6 +786,8 @@ AnimationsTimeline.prototype = {
timeBlock.init(timeBlockEl);
timeBlock.render(animation);
this.timeBlocks.push(timeBlock);
timeBlock.on("selected", this.onAnimationSelected);
}
// Use the document's current time to position the scrubber (if the server
// doesn't provide it, hide the scrubber entirely).
@ -872,15 +896,21 @@ AnimationsTimeline.prototype = {
* UI component responsible for displaying a single animation timeline, which
* basically looks like a rectangle that shows the delay and iterations.
*/
function AnimationTimeBlock() {}
function AnimationTimeBlock() {
EventEmitter.decorate(this);
this.onClick = this.onClick.bind(this);
}
exports.AnimationTimeBlock = AnimationTimeBlock;
AnimationTimeBlock.prototype = {
init: function(containerEl) {
this.containerEl = containerEl;
this.containerEl.addEventListener("click", this.onClick);
},
destroy: function() {
this.containerEl.removeEventListener("click", this.onClick);
while (this.containerEl.firstChild) {
this.containerEl.firstChild.remove();
}
@ -999,6 +1029,10 @@ AnimationTimeBlock.prototype = {
}
return text;
},
onClick: function() {
this.emit("selected", this.animation);
}
};

View File

@ -9,6 +9,7 @@ support-files =
doc_simple_animation.html
head.js
[browser_animation_click_selects_animation.js]
[browser_animation_controller_exposes_document_currentTime.js]
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_mutations_with_same_names.js]

View File

@ -0,0 +1,47 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that animations displayed in the timeline can be selected by clicking
// them, and that this emits the right events and adds the right classes.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let selected = timeline.rootWrapperEl.querySelectorAll(".animation.selected");
ok(!selected.length, "There are no animations selected by default");
info("Click on the first animation, expect the right event and right class");
let animation0 = yield clickToSelect(timeline, 0);
is(animation0, timeline.animations[0],
"The selected event was emitted with the right animation");
ok(isTimeBlockSelected(timeline, 0),
"The time block has the right selected class");
info("Click on the second animation, expect the first one to be unselected");
let animation1 = yield clickToSelect(timeline, 1);
is(animation1, timeline.animations[1],
"The selected event was emitted with the right animation");
ok(isTimeBlockSelected(timeline, 1),
"The second time block has the right selected class");
ok(!isTimeBlockSelected(timeline, 0),
"The first time block has been unselected");
});
function* clickToSelect(timeline, index) {
info("Click on animation " + index + " in the timeline");
let onSelected = timeline.once("selected");
let timeBlock = timeline.rootWrapperEl.querySelectorAll(".time-block")[index];
EventUtils.sendMouseEvent({type: "click"}, timeBlock,
timeBlock.ownerDocument.defaultView);
return yield onSelected;
}
function isTimeBlockSelected(timeline, index) {
let animation = timeline.rootWrapperEl.querySelectorAll(".animation")[index];
return animation.classList.contains("selected");
}

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>

View File

@ -75,6 +75,7 @@ support-files =
[browser_cmd_pref1.js]
[browser_cmd_pref2.js]
[browser_cmd_pref3.js]
[browser_cmd_qsa.js]
[browser_cmd_restart.js]
[browser_cmd_rulers.js]
[browser_cmd_screenshot.js]

View File

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the qsa commands work as they should.
const TEST_URI = "data:text/html;charset=utf-8,<body></body>";
function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.audit(options, [
{
setup: 'qsa',
check: {
input: 'qsa',
hints: ' [query]',
markup: 'VVV',
status: 'VALID'
}
},
{
setup: 'qsa body',
check: {
input: 'qsa body',
hints: '',
markup: 'VVVVVVVV',
status: 'VALID'
}
}
]);
}).then(finish, helpers.handleError);
}

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="debugger.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>

View File

@ -6,6 +6,7 @@
<!ENTITY % toolboxDTD SYSTEM "chrome://devtools/locale/toolbox.dtd" >
%toolboxDTD;
]>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet rel="stylesheet" href="chrome://devtools/content/framework/dev-edition-promo/dev-edition-promo.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="dev-edition-promo">

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>

View File

@ -7,6 +7,8 @@
%toolboxDTD;
]>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="devtools-toolbox-window"
macanimationtype="document"

View File

@ -7,6 +7,8 @@
%toolboxDTD;
]>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="devtools-toolbox-window"
macanimationtype="document"

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/inspector/inspector.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>

View File

@ -6,4 +6,5 @@
<!ENTITY aboutDebugging.addons "Add-ons">
<!ENTITY aboutDebugging.addonDebugging.label "Enable add-on debugging">
<!ENTITY aboutDebugging.addonDebugging.tooltip "Turning this on will allow you to debug add-ons and various other parts of the browser chrome">
<!ENTITY aboutDebugging.loadTemporaryAddon "Load Temporary Add-on">
<!ENTITY aboutDebugging.workers "Workers">

View File

@ -5,6 +5,7 @@
debug = Debug
extensions = Extensions
selectAddonFromFile = Select Add-on Directory or XPI File
serviceWorkers = Service Workers
sharedWorkers = Shared Workers
otherWorkers = Other Workers

View File

@ -18,7 +18,7 @@ add_task(function*() {
markup.isDragging = true;
info("Simulate a mousemove on the view, at the bottom, and expect scrolling");
let onScrolled = waitForViewScroll(markup);
let onScrolled = waitForScrollStop(markup);
markup._onMouseMove({
preventDefault: () => {},
@ -30,7 +30,7 @@ add_task(function*() {
ok(bottomScrollPos > 0, "The view was scrolled down");
info("Simulate a mousemove at the top and expect more scrolling");
onScrolled = waitForViewScroll(markup);
onScrolled = waitForScrollStop(markup);
markup._onMouseMove({
preventDefault: () => {},
@ -46,22 +46,28 @@ add_task(function*() {
markup._onMouseUp();
});
function waitForViewScroll(markup) {
/**
* Waits until the element has not scrolled for 30 consecutive frames.
*/
function* waitForScrollStop(markup) {
let el = markup.doc.documentElement;
let startPos = el.scrollTop;
let win = markup.doc.defaultView;
let lastScrollTop = el.scrollTop;
let stopFrameCount = 0;
while (stopFrameCount < 30) {
// Wait for a frame.
yield new Promise(resolve => win.requestAnimationFrame(resolve));
return new Promise(resolve => {
let isDone = () => {
if (el.scrollTop === startPos) {
resolve(el.scrollTop);
} else {
startPos = el.scrollTop;
// Continue checking every 50ms.
setTimeout(isDone, 50);
}
};
// Check if the element has scrolled.
if (lastScrollTop == el.scrollTop) {
// No scrolling since the last frame.
stopFrameCount++;
} else {
// The element has scrolled. Reset the frame counter.
stopFrameCount = 0;
lastScrollTop = el.scrollTop;
}
}
// Start checking if the view scrolled after a while.
setTimeout(isDone, 50);
});
return lastScrollTop;
}

View File

@ -7,7 +7,7 @@
include('../templates.mozbuild')
DIRS += [
'aboutdebugging/components',
'aboutdebugging',
'animationinspector',
'canvasdebugger',
'commandline',

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/netmonitor/netmonitor.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/shadereditor.css" type="text/css"?>

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>

View File

@ -238,6 +238,10 @@ body {
background-color: var(--even-animation-timeline-background-color);
}
.animation-timeline .animation.selected {
background-color: var(--theme-selection-background-semitransparent);
}
.animation-timeline .animation .target {
width: var(--timeline-sidebar-width);
overflow: hidden;
@ -254,6 +258,7 @@ body {
left: var(--timeline-sidebar-width);
right: 0;
height: var(--timeline-animation-height);
cursor: pointer;
}
/* Animation iterations */

View File

@ -2,6 +2,7 @@
<!-- 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/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>

View File

@ -158,8 +158,12 @@ WebConsoleActor.prototype =
},
/**
* The window we work with.
* @type nsIDOMWindow
* The window or sandbox we work with.
* Note that even if it is named `window` it refers to the current
* global we are debugging, which can be a Sandbox for addons
* or browser content toolbox.
*
* @type nsIDOMWindow or Sandbox
*/
get window() {
if (this.parentActor.isRootActor) {
@ -736,7 +740,8 @@ WebConsoleActor.prototype =
break;
}
let requestStartTime = this.window ?
// See `window` definition. It isn't always a DOM Window.
let requestStartTime = this.window && this.window.performance ?
this.window.performance.timing.requestStart : 0;
let cache = this.consoleAPIListener

View File

@ -69,6 +69,7 @@ exports.devtoolsModules = [
"devtools/shared/gcli/commands/media",
"devtools/shared/gcli/commands/pagemod",
"devtools/shared/gcli/commands/paintflashing",
"devtools/shared/gcli/commands/qsa",
"devtools/shared/gcli/commands/restart",
"devtools/shared/gcli/commands/rulers",
"devtools/shared/gcli/commands/screenshot",

View File

@ -21,6 +21,7 @@ DevToolsModules(
'media.js',
'pagemod.js',
'paintflashing.js',
'qsa.js',
'restart.js',
'rulers.js',
'screenshot.js',

View File

@ -0,0 +1,24 @@
/* 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 l10n = require("gcli/l10n");
exports.items = [
{
item: "command",
runAt: "server",
name: "qsa",
description: l10n.lookup("qsaDesc"),
params: [{
name: "query",
type: "nodelist",
description: l10n.lookup("qsaQueryDesc")
}],
exec: function(args, context) {
return args.query.length;
}
}
];

View File

@ -57,20 +57,7 @@ using JS::ubi::AtomOrTwoByteChars;
/*** Cycle Collection Boilerplate *****************************************************************/
NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)

View File

@ -1557,6 +1557,11 @@ mediaEmulateManual=View the document as if rendered on a device supporting the g
mediaEmulateType=The media type to emulate
mediaResetDesc=Stop emulating a CSS media type
# LOCALIZATION NOTE (qsaDesc, qsaQueryDesc)
# These strings describe the 'qsa' commands and all available parameters.
qsaDesc=Perform querySelectorAll on the current document and return number of matches
qsaQueryDesc=CSS selectors separated by comma
# LOCALIZATION NOTE (injectDesc, injectManual, injectLibraryDesc, injectLoaded,
# injectFailed) These strings describe the 'inject' commands and all available
# parameters.

View File

@ -1058,6 +1058,7 @@ Animation::UpdateEffect()
{
if (mEffect) {
UpdateRelevance();
mEffect->NotifyAnimationTimingUpdated();
}
}

119
dom/animation/EffectSet.cpp Normal file
View File

@ -0,0 +1,119 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "EffectSet.h"
#include "mozilla/dom/Element.h" // For Element
#include "nsCycleCollectionNoteChild.h" // For CycleCollectionNoteChild
namespace mozilla {
/* static */ void
EffectSet::PropertyDtor(void* aObject, nsIAtom* aPropertyName,
void* aPropertyValue, void* aData)
{
EffectSet* effectSet = static_cast<EffectSet*>(aPropertyValue);
#ifdef DEBUG
MOZ_ASSERT(!effectSet->mCalledPropertyDtor, "Should not call dtor twice");
effectSet->mCalledPropertyDtor = true;
#endif
delete effectSet;
}
void
EffectSet::Traverse(nsCycleCollectionTraversalCallback& aCallback)
{
for (auto iter = mEffects.Iter(); !iter.Done(); iter.Next()) {
CycleCollectionNoteChild(aCallback, iter.Get()->GetKey(),
"EffectSet::mEffects[]", aCallback.Flags());
}
}
/* static */ EffectSet*
EffectSet::GetEffectSet(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType)
{
nsIAtom* propName = GetEffectSetPropertyAtom(aPseudoType);
return static_cast<EffectSet*>(aElement->GetProperty(propName));
}
/* static */ EffectSet*
EffectSet::GetOrCreateEffectSet(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType)
{
EffectSet* effectSet = GetEffectSet(aElement, aPseudoType);
if (effectSet) {
return effectSet;
}
nsIAtom* propName = GetEffectSetPropertyAtom(aPseudoType);
effectSet = new EffectSet();
nsresult rv = aElement->SetProperty(propName, effectSet,
&EffectSet::PropertyDtor, true);
if (NS_FAILED(rv)) {
NS_WARNING("SetProperty failed");
// The set must be destroyed via PropertyDtor, otherwise
// mCalledPropertyDtor assertion is triggered in destructor.
EffectSet::PropertyDtor(aElement, propName, effectSet, nullptr);
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
return effectSet;
}
/* static */ nsIAtom**
EffectSet::GetEffectSetPropertyAtoms()
{
static nsIAtom* effectSetPropertyAtoms[] =
{
nsGkAtoms::animationEffectsProperty,
nsGkAtoms::animationEffectsForBeforeProperty,
nsGkAtoms::animationEffectsForAfterProperty,
nullptr
};
return effectSetPropertyAtoms;
}
/* static */ nsIAtom*
EffectSet::GetEffectSetPropertyAtom(nsCSSPseudoElements::Type aPseudoType)
{
switch (aPseudoType) {
case nsCSSPseudoElements::ePseudo_NotPseudoElement:
return nsGkAtoms::animationEffectsProperty;
case nsCSSPseudoElements::ePseudo_before:
return nsGkAtoms::animationEffectsForBeforeProperty;
case nsCSSPseudoElements::ePseudo_after:
return nsGkAtoms::animationEffectsForAfterProperty;
default:
NS_NOTREACHED("Should not try to get animation effects for a pseudo "
"other that :before or :after");
return nullptr;
}
}
void
EffectSet::AddEffect(dom::KeyframeEffectReadOnly& aEffect)
{
mEffects.PutEntry(&aEffect);
}
void
EffectSet::RemoveEffect(dom::KeyframeEffectReadOnly& aEffect)
{
mEffects.RemoveEntry(&aEffect);
}
} // namespace mozilla

136
dom/animation/EffectSet.h Normal file
View File

@ -0,0 +1,136 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_EffectSet_h
#define mozilla_EffectSet_h
#include "nsCSSPseudoElements.h" // For nsCSSPseudoElements::Type
#include "nsHashKeys.h" // For nsPtrHashKey
#include "nsTHashtable.h" // For nsTHashtable
namespace mozilla {
namespace dom {
class Element;
class KeyframeEffectReadOnly;
} // namespace dom
// A wrapper around a hashset of AnimationEffect objects to handle
// storing the set as a property of an element.
class EffectSet
{
public:
EffectSet()
#ifdef DEBUG
: mCalledPropertyDtor(false)
#endif
{
MOZ_COUNT_CTOR(EffectSet);
}
~EffectSet()
{
MOZ_ASSERT(mCalledPropertyDtor,
"must call destructor through element property dtor");
MOZ_COUNT_DTOR(EffectSet);
}
static void PropertyDtor(void* aObject, nsIAtom* aPropertyName,
void* aPropertyValue, void* aData);
// Methods for supporting cycle-collection
void Traverse(nsCycleCollectionTraversalCallback& aCallback);
static EffectSet* GetEffectSet(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType);
static EffectSet* GetOrCreateEffectSet(dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType);
void AddEffect(dom::KeyframeEffectReadOnly& aEffect);
void RemoveEffect(dom::KeyframeEffectReadOnly& aEffect);
private:
typedef nsTHashtable<nsRefPtrHashKey<dom::KeyframeEffectReadOnly>>
OwningEffectSet;
public:
// A simple iterator to support iterating over the effects in this object in
// range-based for loops.
//
// This allows us to avoid exposing mEffects directly and saves the
// caller from having to dereference hashtable iterators using
// the rather complicated: iter.Get()->GetKey().
class Iterator
{
public:
explicit Iterator(OwningEffectSet::Iterator&& aHashIterator)
: mHashIterator(mozilla::Move(aHashIterator))
, mIsEndIterator(false) { }
Iterator(Iterator&& aOther)
: mHashIterator(mozilla::Move(aOther.mHashIterator))
, mIsEndIterator(aOther.mIsEndIterator) { }
static Iterator EndIterator(OwningEffectSet::Iterator&& aHashIterator)
{
Iterator result(mozilla::Move(aHashIterator));
result.mIsEndIterator = true;
return result;
}
bool operator!=(const Iterator& aOther) const {
if (Done() || aOther.Done()) {
return Done() != aOther.Done();
}
return mHashIterator.Get() != aOther.mHashIterator.Get();
}
Iterator& operator++() {
MOZ_ASSERT(!Done());
mHashIterator.Next();
return *this;
}
dom::KeyframeEffectReadOnly* operator* ()
{
MOZ_ASSERT(!Done());
return mHashIterator.Get()->GetKey();
}
private:
Iterator() = delete;
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
Iterator& operator=(const Iterator&&) = delete;
bool Done() const {
return mIsEndIterator || mHashIterator.Done();
}
OwningEffectSet::Iterator mHashIterator;
bool mIsEndIterator;
};
Iterator begin() { return Iterator(mEffects.Iter()); }
Iterator end()
{
return Iterator::EndIterator(mEffects.Iter());
}
static nsIAtom** GetEffectSetPropertyAtoms();
private:
static nsIAtom* GetEffectSetPropertyAtom(nsCSSPseudoElements::Type
aPseudoType);
OwningEffectSet mEffects;
#ifdef DEBUG
bool mCalledPropertyDtor;
#endif
};
} // namespace mozilla
#endif // mozilla_EffectSet_h

View File

@ -127,6 +127,9 @@ KeyframeEffectReadOnly::SetTiming(const AnimationTiming& aTiming)
if (mAnimation) {
mAnimation->NotifyEffectTimingUpdated();
}
// NotifyEffectTimingUpdated will eventually cause
// NotifyAnimationTimingUpdated to be called on this object which will
// update our registration with the target element.
}
Nullable<TimeDuration>
@ -336,6 +339,7 @@ void
KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
{
mAnimation = aAnimation;
NotifyAnimationTimingUpdated();
}
const AnimationProperty*
@ -511,8 +515,6 @@ KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSProperty aProperty,
}
}
// We need to define this here since Animation is an incomplete type
// (forward-declared) in the header.
KeyframeEffectReadOnly::~KeyframeEffectReadOnly()
{
}
@ -525,6 +527,34 @@ KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
}
}
void
KeyframeEffectReadOnly::UpdateTargetRegistration()
{
if (!mTarget) {
return;
}
bool isRelevant = mAnimation && mAnimation->IsRelevant();
// Animation::IsRelevant() returns a cached value. It only updates when
// something calls Animation::UpdateRelevance. Whenever our timing changes,
// we should be notifying our Animation before calling this, so
// Animation::IsRelevant() should be up-to-date by the time we get here.
MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(),
"Out of date Animation::IsRelevant value");
if (isRelevant) {
EffectSet* effectSet = EffectSet::GetOrCreateEffectSet(mTarget,
mPseudoType);
effectSet->AddEffect(*this);
} else {
EffectSet* effectSet = EffectSet::GetEffectSet(mTarget, mPseudoType);
if (effectSet) {
effectSet->RemoveEffect(*this);
}
}
}
#ifdef DEBUG
void
DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)

View File

@ -219,6 +219,7 @@ public:
const AnimationTiming& Timing() const { return mTiming; }
AnimationTiming& Timing() { return mTiming; }
void SetTiming(const AnimationTiming& aTiming);
void NotifyAnimationTimingUpdated() { UpdateTargetRegistration(); }
Nullable<TimeDuration> GetLocalTime() const;
@ -256,6 +257,7 @@ public:
bool IsInEffect() const;
void SetAnimation(Animation* aAnimation);
Animation* GetAnimation() const { return mAnimation; }
const AnimationProperty*
GetAnimationOfProperty(nsCSSProperty aProperty) const;
@ -297,6 +299,17 @@ protected:
virtual ~KeyframeEffectReadOnly();
void ResetIsRunningOnCompositor();
// This effect is registered with its target element so long as:
//
// (a) It has a target element, and
// (b) It is "relevant" (i.e. yet to finish but not idle, or finished but
// filling forwards)
//
// As a result, we need to make sure this gets called whenever anything
// changes with regards to this effects's timing including changes to the
// owning Animation's timing.
void UpdateTargetRegistration();
static AnimationTiming ConvertKeyframeEffectOptions(
const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions);

View File

@ -19,6 +19,7 @@ EXPORTS.mozilla += [
'AnimationComparator.h',
'AnimationUtils.h',
'ComputedTimingFunction.h',
'EffectSet.h',
'PendingAnimationTracker.h',
]
@ -28,6 +29,7 @@ UNIFIED_SOURCES += [
'AnimationTimeline.cpp',
'ComputedTimingFunction.cpp',
'DocumentTimeline.cpp',
'EffectSet.cpp',
'KeyframeEffect.cpp',
'PendingAnimationTracker.cpp',
]

View File

@ -54,6 +54,7 @@
#include "mozilla/AnimationComparator.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
@ -3305,24 +3306,22 @@ Element::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations)
doc->FlushPendingNotifications(Flush_Style);
}
nsIAtom* properties[] = { nsGkAtoms::transitionsProperty,
nsGkAtoms::animationsProperty };
for (size_t propIdx = 0; propIdx < MOZ_ARRAY_LENGTH(properties);
propIdx++) {
AnimationCollection* collection =
static_cast<AnimationCollection*>(
GetProperty(properties[propIdx]));
if (!collection) {
continue;
}
for (size_t animIdx = 0;
animIdx < collection->mAnimations.Length();
animIdx++) {
Animation* anim = collection->mAnimations[animIdx];
if (anim->IsRelevant()) {
aAnimations.AppendElement(anim);
}
}
EffectSet* effects = EffectSet::GetEffectSet(this,
nsCSSPseudoElements::ePseudo_NotPseudoElement);
if (!effects) {
return;
}
for (KeyframeEffectReadOnly* effect : *effects) {
MOZ_ASSERT(effect && effect->GetAnimation(),
"Only effects associated with an animation should be "
"added to an element's effect set");
Animation* animation = effect->GetAnimation();
MOZ_ASSERT(animation->IsRelevant(),
"Only relevant animations should be added to an element's "
"effect set");
aAnimations.AppendElement(animation);
}
aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());

View File

@ -18,6 +18,7 @@
#include "mozilla/dom/FragmentOrElement.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
@ -1351,6 +1352,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
for (uint32_t i = 0; props[i]; ++i) {
tmp->DeleteProperty(*props[i]);
}
// Bug 1226091: Call MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
tmp->DeleteProperty(effectProps[i]);
}
}
}
@ -1901,6 +1907,15 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
static_cast<nsISupports*>(tmp->GetProperty(*props[i]));
cb.NoteXPCOMChild(property);
}
// Bug 1226091: Check MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
EffectSet* effectSet =
static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
if (effectSet) {
effectSet->Traverse(cb);
}
}
}
}

View File

@ -2099,6 +2099,9 @@ GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*
GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*
GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*

View File

@ -148,10 +148,12 @@ CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRe
* Encapsulate the given _aSurface_ into a layers::CairoImage.
*/
static already_AddRefed<layers::Image>
CreateImageFromSurface(SourceSurface* aSurface, ErrorResult& aRv)
CreateImageFromSurface(SourceSurface* aSurface)
{
MOZ_ASSERT(aSurface);
RefPtr<layers::CairoImage> image = new layers::CairoImage(aSurface->GetSize(), aSurface);
RefPtr<layers::CairoImage> image =
new layers::CairoImage(aSurface->GetSize(), aSurface);
return image.forget();
}
@ -166,8 +168,7 @@ CreateSurfaceFromRawData(const gfx::IntSize& aSize,
gfx::SurfaceFormat aFormat,
uint8_t* aBuffer,
uint32_t aBufferLength,
const Maybe<IntRect>& aCropRect,
ErrorResult& aRv)
const Maybe<IntRect>& aCropRect)
{
MOZ_ASSERT(!aSize.IsEmpty());
MOZ_ASSERT(aBuffer);
@ -177,7 +178,6 @@ CreateSurfaceFromRawData(const gfx::IntSize& aSize,
Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat);
if (NS_WARN_IF(!dataSurface)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -189,7 +189,6 @@ CreateSurfaceFromRawData(const gfx::IntSize& aSize,
RefPtr<DataSourceSurface> result = CropAndCopyDataSourceSurface(dataSurface, cropRect);
if (NS_WARN_IF(!result)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -202,8 +201,7 @@ CreateImageFromRawData(const gfx::IntSize& aSize,
gfx::SurfaceFormat aFormat,
uint8_t* aBuffer,
uint32_t aBufferLength,
const Maybe<IntRect>& aCropRect,
ErrorResult& aRv)
const Maybe<IntRect>& aCropRect)
{
MOZ_ASSERT(NS_IsMainThread());
@ -211,9 +209,9 @@ CreateImageFromRawData(const gfx::IntSize& aSize,
RefPtr<SourceSurface> rgbaSurface =
CreateSurfaceFromRawData(aSize, aStride, aFormat,
aBuffer, aBufferLength,
aCropRect, aRv);
aCropRect);
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!rgbaSurface)) {
return nullptr;
}
@ -241,7 +239,11 @@ CreateImageFromRawData(const gfx::IntSize& aSize,
bgraDataSurface->Unmap();
// Create an Image from the BGRA SourceSurface.
RefPtr<layers::Image> image = CreateImageFromSurface(bgraDataSurface, aRv);
RefPtr<layers::Image> image = CreateImageFromSurface(bgraDataSurface);
if (NS_WARN_IF(!image)) {
return nullptr;
}
return image.forget();
}
@ -266,7 +268,6 @@ public:
gfx::SurfaceFormat aFormat,
const gfx::IntSize& aSize,
const Maybe<IntRect>& aCropRect,
ErrorResult& aError,
layers::Image** aImage)
: WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate())
, mImage(aImage)
@ -276,8 +277,8 @@ public:
, mFormat(aFormat)
, mSize(aSize)
, mCropRect(aCropRect)
, mError(aError)
{
MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromRawDataInMainThreadSyncTask.");
}
bool MainThreadRun() override
@ -285,10 +286,9 @@ public:
RefPtr<layers::Image> image =
CreateImageFromRawData(mSize, mStride, mFormat,
mBuffer, mBufferLength,
mCropRect,
mError);
mCropRect);
if (NS_WARN_IF(mError.Failed())) {
if (NS_WARN_IF(!image)) {
return false;
}
@ -305,7 +305,6 @@ private:
gfx::SurfaceFormat mFormat;
gfx::IntSize mSize;
const Maybe<IntRect>& mCropRect;
ErrorResult& mError;
};
static bool
@ -519,9 +518,10 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl
}
// Create ImageBitmap.
RefPtr<layers::Image> data = CreateImageFromSurface(surface, aRv);
RefPtr<layers::Image> data = CreateImageFromSurface(surface);
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!data)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -623,9 +623,10 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvas
}
// Create an Image from the SourceSurface.
RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface, aRv);
RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface);
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!data)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -669,7 +670,7 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
if (NS_IsMainThread()) {
data = CreateImageFromRawData(imageSize, imageStride, FORMAT,
array.Data(), dataLength,
aCropRect, aRv);
aCropRect);
} else {
RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task
= new CreateImageFromRawDataInMainThreadSyncTask(array.Data(),
@ -678,12 +679,12 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
FORMAT,
imageSize,
aCropRect,
aRv,
getter_AddRefs(data));
task->Dispatch(aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!data)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -719,9 +720,10 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D&
return nullptr;
}
RefPtr<layers::Image> data = CreateImageFromSurface(surface, aRv);
RefPtr<layers::Image> data = CreateImageFromSurface(surface);
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!data)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -823,12 +825,13 @@ AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
}
static already_AddRefed<SourceSurface>
DecodeBlob(Blob& aBlob, ErrorResult& aRv)
DecodeBlob(Blob& aBlob)
{
// Get the internal stream of the blob.
nsCOMPtr<nsIInputStream> stream;
aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
ErrorResult error;
aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), error);
if (NS_WARN_IF(error.Failed())) {
return nullptr;
}
@ -840,7 +843,6 @@ DecodeBlob(Blob& aBlob, ErrorResult& aRv)
// Get the Component object.
nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
if (NS_WARN_IF(!imgtool)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -848,8 +850,7 @@ DecodeBlob(Blob& aBlob, ErrorResult& aRv)
NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16); // NS_ConvertUTF16toUTF8 ---|> nsAutoCString
nsCOMPtr<imgIContainer> imgContainer;
nsresult rv = imgtool->DecodeImage(stream, mimeTypeUTF8, getter_AddRefs(imgContainer));
if (NS_FAILED(rv)) {
aRv.Throw(rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
@ -859,7 +860,6 @@ DecodeBlob(Blob& aBlob, ErrorResult& aRv)
RefPtr<SourceSurface> surface = imgContainer->GetFrame(whichFrame, frameFlags);
if (NS_WARN_IF(!surface)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -867,12 +867,12 @@ DecodeBlob(Blob& aBlob, ErrorResult& aRv)
}
static already_AddRefed<layers::Image>
DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect, ErrorResult& aRv)
DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect)
{
// Decode the blob into a SourceSurface.
RefPtr<SourceSurface> surface = DecodeBlob(aBlob, aRv);
RefPtr<SourceSurface> surface = DecodeBlob(aBlob);
if (NS_WARN_IF(aRv.Failed())) {
if (NS_WARN_IF(!surface)) {
return nullptr;
}
@ -898,12 +898,15 @@ DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect, ErrorResult& aRv)
}
if (NS_WARN_IF(!croppedSurface)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
// Create an Image from the source surface.
RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface, aRv);
RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface);
if (NS_WARN_IF(!image)) {
return nullptr;
}
return image.forget();
}
@ -985,11 +988,10 @@ public:
private:
already_AddRefed<ImageBitmap> CreateImageBitmap() override
{
ErrorResult rv;
RefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect, rv);
RefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect);
if (NS_WARN_IF(rv.Failed())) {
mPromise->MaybeReject(rv);
if (NS_WARN_IF(!data)) {
mPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
@ -1009,21 +1011,19 @@ class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnabl
DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate,
Blob& aBlob,
Maybe<IntRect>& aCropRect,
ErrorResult& aError,
layers::Image** aImage)
: WorkerMainThreadRunnable(aWorkerPrivate)
, mBlob(aBlob)
, mCropRect(aCropRect)
, mError(aError)
, mImage(aImage)
{
}
bool MainThreadRun() override
{
RefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect, mError);
RefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect);
if (NS_WARN_IF(mError.Failed())) {
if (NS_WARN_IF(!image)) {
return true;
}
@ -1035,7 +1035,6 @@ class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnabl
private:
Blob& mBlob;
Maybe<IntRect>& mCropRect;
ErrorResult& mError;
layers::Image** mImage;
};
@ -1062,7 +1061,7 @@ private:
ErrorResult rv;
RefPtr<DecodeBlobInMainThreadSyncTask> task =
new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect,
rv, getter_AddRefs(data));
getter_AddRefs(data));
task->Dispatch(rv); // This is a synchronous call.
if (NS_WARN_IF(rv.Failed())) {
@ -1070,6 +1069,11 @@ private:
mPromise->MaybeReject(rv);
return nullptr;
}
if (NS_WARN_IF(!data)) {
mPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
// Create ImageBitmap object.
RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);

View File

@ -1,2 +0,0 @@
// Ensure the contacts service is running in the parent.
Components.utils.import("resource://gre/modules/ContactService.jsm");

View File

@ -1,5 +1,5 @@
[DEFAULT]
support-files = shared.js contacts_chromescript.js
support-files = shared.js
[test_contacts_basics.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only failure

View File

@ -1,11 +1,7 @@
"use strict";
// Fix the environment to run Contacts tests
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/ContactService.jsm");
} else {
SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('contacts_chromescript.js'));
}
SpecialPowers.importInMainProcess("resource://gre/modules/ContactService.jsm");
// Some helpful global vars
var isAndroid = (navigator.userAgent.indexOf("Android") !== -1);

View File

@ -482,6 +482,30 @@ EventStateManager::TryToFlushPendingNotificationsToIME()
}
}
static bool
IsMessageMouseUserActivity(EventMessage aMessage)
{
return aMessage == eMouseMove ||
aMessage == eMouseUp ||
aMessage == eMouseDown ||
aMessage == eMouseDoubleClick ||
aMessage == eMouseClick ||
aMessage == eMouseActivate ||
aMessage == eMouseLongTap;
}
static bool
IsMessageGamepadUserActivity(EventMessage aMessage)
{
#ifndef MOZ_GAMEPAD
return false;
#else
return aMessage == eGamepadButtonDown ||
aMessage == eGamepadButtonUp ||
aMessage == eGamepadAxisMove;
#endif
}
nsresult
EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
WidgetEvent* aEvent,
@ -510,12 +534,12 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (aEvent->mFlags.mIsTrusted &&
((mouseEvent && mouseEvent->IsReal() &&
mouseEvent->mMessage != eMouseEnterIntoWidget &&
mouseEvent->mMessage != eMouseExitFromWidget) ||
IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
aEvent->mClass == eWheelEventClass ||
aEvent->mClass == ePointerEventClass ||
aEvent->mClass == eTouchEventClass ||
aEvent->mClass == eKeyboardEventClass)) {
aEvent->mClass == eKeyboardEventClass ||
IsMessageGamepadUserActivity(aEvent->mMessage))) {
if (gMouseOrKeyboardEventCounter == 0) {
nsCOMPtr<nsIObserverService> obs =
mozilla::services::GetObserverService();

View File

@ -333,6 +333,7 @@ PositionError::NotifyCallback(const GeoPositionErrorCallback& aCallback)
if (callback) {
ErrorResult err;
callback->Call(*this, err);
err.SuppressException();
}
} else {
nsIDOMGeoPositionErrorCallback* callback = aCallback.GetXPCOMCallback();
@ -661,6 +662,7 @@ nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
MOZ_ASSERT(callback);
callback->Call(*wrapped, err);
err.SuppressException();
} else {
nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();

View File

@ -4764,8 +4764,7 @@ HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
}
// If we are actually playing...
if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
!IsPlaybackEnded()) {
if (IsCurrentlyPlaying()) {
return true;
}
@ -5119,5 +5118,26 @@ HTMLMediaElement::ComputedMuted() const
return (mMuted & MUTED_BY_AUDIO_CHANNEL);
}
bool
HTMLMediaElement::IsCurrentlyPlaying() const
{
// We have playable data, but we still need to check whether data is "real"
// current data.
if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
!IsPlaybackEnded()) {
// Restart the video after ended, it needs to seek to the new position.
// In b2g, the cache is not large enough to store whole video data, so we
// need to download data again. In this case, although the ready state is
// "HAVE_CURRENT_DATA", it is the previous old data. Actually we are not
// yet have enough currently data.
if (mDecoder && mDecoder->IsSeeking() && !mPlayingBeforeSeek) {
return false;
}
return true;
}
return false;
}
} // namespace dom
} // namespace mozilla

View File

@ -1070,6 +1070,9 @@ protected:
// A method to check if we are playing through the AudioChannel.
bool IsPlayingThroughTheAudioChannel() const;
// A method to check whether we are currently playing.
bool IsCurrentlyPlaying() const;
// Update the audio channel playing state
void UpdateAudioChannelPlayingState();

View File

@ -153,16 +153,9 @@ function startTest() {
}
}
// test_ipc.html executes all the tests in this directory in content process.
// It will fail on this one for the moment.
if (!SpecialPowers.isMainProcess()) {
todo(false, "We should make this work on content process");
SimpleTest.finish();
} else {
// TODO: remove unsetting network.disable.ipc.security as part of bug 820712
SpecialPowers.pushPrefEnv({
"set": [
["network.disable.ipc.security", true],
]
}, startTest);
}
// TODO: remove unsetting network.disable.ipc.security as part of bug 820712
SpecialPowers.pushPrefEnv({
"set": [
["network.disable.ipc.security", true],
]
}, startTest);

Some files were not shown because too many files have changed in this diff Show More