mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
0a41d103c4
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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/");
|
||||
|
@ -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();
|
||||
}
|
||||
}]
|
||||
);
|
||||
|
@ -1,5 +0,0 @@
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % passwordManagerDTD SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
|
||||
%passwordManagerDTD;
|
||||
]>
|
||||
<window>&savedLogins.title;</window>
|
@ -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]
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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)
|
||||
|
@ -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"));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -64,6 +64,8 @@ var LoopMochaUtils = (function(global, _) {
|
||||
throw result;
|
||||
};
|
||||
|
||||
this.catch = function() {};
|
||||
|
||||
asyncFn(this.resolve.bind(this), this.reject.bind(this));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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/
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
});
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"],
|
||||
|
@ -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);
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"));
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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'
|
||||
]
|
7
devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
vendored
Normal file
7
devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
vendored
Normal 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() {}
|
@ -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>
|
9
devtools/client/aboutdebugging/test/browser.ini
Normal file
9
devtools/client/aboutdebugging/test/browser.ini
Normal 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]
|
@ -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);
|
||||
});
|
78
devtools/client/aboutdebugging/test/head.js
Normal file
78
devtools/client/aboutdebugging/test/head.js
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
}
|
@ -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"?>
|
||||
|
@ -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]
|
||||
|
33
devtools/client/commandline/test/browser_cmd_qsa.js
Normal file
33
devtools/client/commandline/test/browser_cmd_qsa.js
Normal 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);
|
||||
}
|
@ -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"?>
|
||||
|
@ -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">
|
||||
|
@ -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"?>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"?>
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
include('../templates.mozbuild')
|
||||
|
||||
DIRS += [
|
||||
'aboutdebugging/components',
|
||||
'aboutdebugging',
|
||||
'animationinspector',
|
||||
'canvasdebugger',
|
||||
'commandline',
|
||||
|
@ -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"?>
|
||||
|
@ -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"?>
|
||||
|
@ -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"?>
|
||||
|
@ -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"?>
|
||||
|
@ -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"?>
|
||||
|
@ -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 */
|
||||
|
@ -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"?>
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -21,6 +21,7 @@ DevToolsModules(
|
||||
'media.js',
|
||||
'pagemod.js',
|
||||
'paintflashing.js',
|
||||
'qsa.js',
|
||||
'restart.js',
|
||||
'rulers.js',
|
||||
'screenshot.js',
|
||||
|
24
devtools/shared/gcli/commands/qsa.js
Normal file
24
devtools/shared/gcli/commands/qsa.js
Normal 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;
|
||||
}
|
||||
}
|
||||
];
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -1058,6 +1058,7 @@ Animation::UpdateEffect()
|
||||
{
|
||||
if (mEffect) {
|
||||
UpdateRelevance();
|
||||
mEffect->NotifyAnimationTimingUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
|
119
dom/animation/EffectSet.cpp
Normal file
119
dom/animation/EffectSet.cpp
Normal 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
136
dom/animation/EffectSet.h
Normal 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
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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>>());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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*
|
||||
|
@ -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);
|
||||
|
@ -1,2 +0,0 @@
|
||||
// Ensure the contacts service is running in the parent.
|
||||
Components.utils.import("resource://gre/modules/ContactService.jsm");
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user