mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Merge m-c to s-c
This commit is contained in:
commit
00a971da6b
@ -878,3 +878,13 @@ pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
|
||||
// Gaia relies heavily on scroll events for now, so lets fire them
|
||||
// more often than the default value (100).
|
||||
pref("apz.asyncscroll.throttle", 40);
|
||||
|
||||
// This preference allows FirefoxOS apps (and content, I think) to force
|
||||
// the use of software (instead of hardware accelerated) 2D canvases by
|
||||
// creating a context like this:
|
||||
//
|
||||
// canvas.getContext('2d', { willReadFrequently: true })
|
||||
//
|
||||
// Using a software canvas can save memory when JS calls getImageData()
|
||||
// on the canvas frequently. See bug 884226.
|
||||
pref("gfx.canvas.willReadFrequently.enable", true);
|
||||
|
@ -8,6 +8,8 @@ let Cu = Components.utils;
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
dump("############ ErrorPage.js\n");
|
||||
|
||||
let ErrorPageHandler = {
|
||||
_reload: function() {
|
||||
docShell.QueryInterface(Ci.nsIWebNavigation).reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
|
||||
@ -31,12 +33,8 @@ let ErrorPageHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
domContentLoadedHandler: function(e) {
|
||||
let target = e.originalTarget;
|
||||
let targetDocShell = target.defaultView
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation);
|
||||
if (targetDocShell != docShell) {
|
||||
_bindPageEvent: function(target) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -52,11 +50,23 @@ let ErrorPageHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
domContentLoadedHandler: function(e) {
|
||||
let target = e.originalTarget;
|
||||
let targetDocShell = target.defaultView
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation);
|
||||
if (targetDocShell != docShell) {
|
||||
return;
|
||||
}
|
||||
this._bindPageEvent(target);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
addMessageListener("ErrorPage:ReloadPage", this._reload.bind(this));
|
||||
addEventListener('DOMContentLoaded',
|
||||
this.domContentLoadedHandler.bind(this),
|
||||
true);
|
||||
this._bindPageEvent(content.document);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -149,6 +149,26 @@ let ErrorPage = {
|
||||
}
|
||||
},
|
||||
|
||||
_listenError: function(frameLoader) {
|
||||
let self = this;
|
||||
let frameElement = frameLoader.ownerElement;
|
||||
let injectErrorPageScript = function() {
|
||||
let mm = frameLoader.messageManager;
|
||||
try {
|
||||
mm.loadFrameScript(kErrorPageFrameScript, true, true);
|
||||
} catch (e) {
|
||||
dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
|
||||
}
|
||||
mm.addMessageListener('ErrorPage:AddCertException', self._addCertException.bind(self));
|
||||
frameElement.removeEventListener('mozbrowsererror', injectErrorPageScript, true);
|
||||
};
|
||||
|
||||
frameElement.addEventListener('mozbrowsererror',
|
||||
injectErrorPageScript,
|
||||
true // use capture
|
||||
);
|
||||
},
|
||||
|
||||
init: function errorPageInit() {
|
||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
||||
@ -156,17 +176,7 @@ let ErrorPage = {
|
||||
|
||||
observe: function errorPageObserve(aSubject, aTopic, aData) {
|
||||
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
||||
let mm = frameLoader.messageManager;
|
||||
|
||||
// This won't happen from dom/ipc/preload.js in non-OOP builds.
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("dom.ipc.tabs.disabled") === true) {
|
||||
mm.loadFrameScript(kErrorPageFrameScript, true, true);
|
||||
}
|
||||
} catch (e) {
|
||||
dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
|
||||
}
|
||||
mm.addMessageListener('ErrorPage:AddCertException', this._addCertException.bind(this));
|
||||
this._listenError(frameLoader);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "ac80bbe24c39a813ab65b98bb56dce2e42207b22",
|
||||
"revision": "d5d2b5614c32e17c5c3c736181a66bfa3fc5c3ef",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -202,16 +202,8 @@
|
||||
align="start"
|
||||
orient="vertical"
|
||||
role="alert">
|
||||
<hbox>
|
||||
<vbox>
|
||||
<image id="UITourTooltipIcon"/>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<label id="UITourTooltipTitle" flex="1"/>
|
||||
<description id="UITourTooltipDescription" flex="1"/>
|
||||
<hbox id="UITourTooltipButtons" flex="1" align="end"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<label id="UITourTooltipTitle" flex="1"/>
|
||||
<description id="UITourTooltipDescription" flex="1"/>
|
||||
</panel>
|
||||
<panel id="UITourHighlightContainer"
|
||||
hidden="true"
|
||||
|
@ -108,9 +108,9 @@ skip-if = toolkit == "gtk2" || toolkit == "gtk3" # browser_CTP_context_menu.js
|
||||
run-if = crashreporter
|
||||
[browser_CTP_data_urls.js]
|
||||
[browser_CTP_drag_drop.js]
|
||||
[browser_CTP_hideBar.js]
|
||||
[browser_CTP_multi_allow.js]
|
||||
[browser_CTP_nonplugins.js]
|
||||
[browser_CTP_notificationBar.js]
|
||||
[browser_CTP_resize.js]
|
||||
[browser_URLBarSetURI.js]
|
||||
[browser_aboutAccounts.js]
|
||||
|
@ -1,98 +0,0 @@
|
||||
var rootDir = getRootDirectory(gTestPath);
|
||||
const gTestRoot = rootDir;
|
||||
const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
|
||||
|
||||
var gTestBrowser = null;
|
||||
var gNextTest = null;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function() {
|
||||
clearAllPluginPermissions();
|
||||
Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
|
||||
});
|
||||
Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
|
||||
|
||||
var newTab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = newTab;
|
||||
gTestBrowser = gBrowser.selectedBrowser;
|
||||
gTestBrowser.addEventListener("load", pageLoad, true);
|
||||
|
||||
Services.prefs.setBoolPref("plugins.click_to_play", true);
|
||||
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
|
||||
|
||||
prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_small.html");
|
||||
}
|
||||
|
||||
function finishTest() {
|
||||
gTestBrowser.removeEventListener("load", pageLoad, true);
|
||||
gBrowser.removeCurrentTab();
|
||||
window.focus();
|
||||
finish();
|
||||
}
|
||||
|
||||
function pageLoad() {
|
||||
// The plugin events are async dispatched and can come after the load event
|
||||
// This just allows the events to fire before we then go on to test the states
|
||||
executeSoon(gNextTest);
|
||||
}
|
||||
|
||||
function prepareTest(nextTest, url) {
|
||||
gNextTest = nextTest;
|
||||
gTestBrowser.contentWindow.location = url;
|
||||
}
|
||||
|
||||
// Due to layout being async, "PluginBindAttached" may trigger later.
|
||||
// This wraps a function to force a layout flush, thus triggering it,
|
||||
// and schedules the function execution so they're definitely executed
|
||||
// afterwards.
|
||||
function runAfterPluginBindingAttached(func) {
|
||||
return function() {
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let elems = doc.getElementsByTagName('embed');
|
||||
if (elems.length < 1) {
|
||||
elems = doc.getElementsByTagName('object');
|
||||
}
|
||||
elems[0].clientTop;
|
||||
executeSoon(func);
|
||||
};
|
||||
}
|
||||
|
||||
// Test that the notification bar is getting dismissed when directly activating plugins
|
||||
// via the doorhanger.
|
||||
|
||||
function test1() {
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test2,
|
||||
"Test 1, expected a notification bar for hidden plugins");
|
||||
}
|
||||
|
||||
function test2() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
|
||||
ok(notification, "Test 2, Should have a click-to-play notification");
|
||||
let plugin = gTestBrowser.contentDocument.getElementById("test");
|
||||
ok(plugin, "Test 2, Found plugin in page");
|
||||
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
|
||||
"Test 2, Plugin should be click-to-play");
|
||||
|
||||
// simulate "always allow"
|
||||
notification.reshow();
|
||||
PopupNotifications.panel.firstChild._primaryButton.click();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
|
||||
test3,
|
||||
"Test 2, expected the notification bar for hidden plugins to get dismissed");
|
||||
}
|
||||
|
||||
function test3() {
|
||||
let plugin = gTestBrowser.contentDocument.getElementById("test");
|
||||
ok(plugin, "Test 3, Found plugin in page");
|
||||
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
waitForCondition(() => objLoadingContent.activated, finishTest,
|
||||
"Test 3, Waited too long for plugin to activate");
|
||||
}
|
179
browser/base/content/test/general/browser_CTP_notificationBar.js
Normal file
179
browser/base/content/test/general/browser_CTP_notificationBar.js
Normal file
@ -0,0 +1,179 @@
|
||||
var rootDir = getRootDirectory(gTestPath);
|
||||
const gTestRoot = rootDir;
|
||||
const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
|
||||
|
||||
var gTestBrowser = null;
|
||||
var gNextTest = null;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function() {
|
||||
clearAllPluginPermissions();
|
||||
Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
|
||||
});
|
||||
Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
|
||||
|
||||
var newTab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = newTab;
|
||||
gTestBrowser = gBrowser.selectedBrowser;
|
||||
gTestBrowser.addEventListener("load", pageLoad, true);
|
||||
|
||||
Services.prefs.setBoolPref("plugins.click_to_play", true);
|
||||
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
|
||||
|
||||
prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_small.html");
|
||||
}
|
||||
|
||||
function finishTest() {
|
||||
gTestBrowser.removeEventListener("load", pageLoad, true);
|
||||
gBrowser.removeCurrentTab();
|
||||
window.focus();
|
||||
finish();
|
||||
}
|
||||
|
||||
function pageLoad() {
|
||||
// The plugin events are async dispatched and can come after the load event
|
||||
// This just allows the events to fire before we then go on to test the states
|
||||
executeSoon(gNextTest);
|
||||
}
|
||||
|
||||
function prepareTest(nextTest, url) {
|
||||
gNextTest = nextTest;
|
||||
gTestBrowser.contentWindow.location = url;
|
||||
}
|
||||
|
||||
// Due to layout being async, "PluginBindAttached" may trigger later.
|
||||
// This wraps a function to force a layout flush, thus triggering it,
|
||||
// and schedules the function execution so they're definitely executed
|
||||
// afterwards.
|
||||
function runAfterPluginBindingAttached(func) {
|
||||
return function() {
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let elems = doc.getElementsByTagName('embed');
|
||||
if (elems.length < 1) {
|
||||
elems = doc.getElementsByTagName('object');
|
||||
}
|
||||
elems[0].clientTop;
|
||||
executeSoon(func);
|
||||
};
|
||||
}
|
||||
|
||||
// Tests for the notification bar for hidden plugins.
|
||||
|
||||
function test1() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 1: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
() => {
|
||||
// Don't use setTestPluginEnabledState here because we already saved the
|
||||
// prior value
|
||||
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
|
||||
prepareTest(test2, gTestRoot + "plugin_small.html");
|
||||
},
|
||||
"Test 1, expected to have a plugin notification bar");
|
||||
}
|
||||
|
||||
function test2() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 2: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
|
||||
() => {
|
||||
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
|
||||
prepareTest(test3, gTestRoot + "plugin_overlayed.html");
|
||||
},
|
||||
"Test 2, expected to not have a plugin notification bar");
|
||||
}
|
||||
|
||||
function test3() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 3: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test3b,
|
||||
"Test 3, expected the plugin infobar to be triggered when plugin was overlayed");
|
||||
}
|
||||
|
||||
function test3b()
|
||||
{
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let plugin = doc.getElementById("test");
|
||||
ok(plugin, "Test 3b, Found plugin in page");
|
||||
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
|
||||
"Test 3b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
|
||||
ok(!plugin.activated, "Test 3b, Plugin should not be activated");
|
||||
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
|
||||
ok(!overlay.classList.contains("visible"), "Test 3b, Plugin overlay should be hidden");
|
||||
|
||||
prepareTest(test4, gTestRoot + "plugin_positioned.html");
|
||||
}
|
||||
|
||||
function test4() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 4: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test4b,
|
||||
"Test 4, expected the plugin infobar to be triggered when plugin was overlayed");
|
||||
}
|
||||
|
||||
function test4b() {
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let plugin = doc.getElementById("test");
|
||||
ok(plugin, "Test 4b, Found plugin in page");
|
||||
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
|
||||
"Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
|
||||
ok(!plugin.activated, "Test 4b, Plugin should not be activated");
|
||||
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
|
||||
ok(!overlay.classList.contains("visible"), "Test 4b, Plugin overlay should be hidden");
|
||||
|
||||
prepareTest(runAfterPluginBindingAttached(test5), gHttpTestRoot + "plugin_small.html");
|
||||
}
|
||||
|
||||
// Test that the notification bar is getting dismissed when directly activating plugins
|
||||
// via the doorhanger.
|
||||
|
||||
function test5() {
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test6,
|
||||
"Test 5, expected a notification bar for hidden plugins");
|
||||
}
|
||||
|
||||
function test6() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
|
||||
ok(notification, "Test 6, Should have a click-to-play notification");
|
||||
let plugin = gTestBrowser.contentDocument.getElementById("test");
|
||||
ok(plugin, "Test 6, Found plugin in page");
|
||||
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
|
||||
"Test 6, Plugin should be click-to-play");
|
||||
|
||||
// simulate "always allow"
|
||||
notification.reshow();
|
||||
PopupNotifications.panel.firstChild._primaryButton.click();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
|
||||
test7,
|
||||
"Test 6, expected the notification bar for hidden plugins to get dismissed");
|
||||
}
|
||||
|
||||
function test7() {
|
||||
let plugin = gTestBrowser.contentDocument.getElementById("test");
|
||||
ok(plugin, "Test 7, Found plugin in page");
|
||||
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
waitForCondition(() => objLoadingContent.activated, finishTest,
|
||||
"Test 7, Waited too long for plugin to activate");
|
||||
}
|
@ -844,82 +844,5 @@ function test25() {
|
||||
ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
|
||||
ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
|
||||
|
||||
prepareTest(() => executeSoon(test26), gTestRoot + "plugin_small.html");
|
||||
}
|
||||
|
||||
function test26() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 26: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
() => {
|
||||
// Don't use setTestPluginEnabledState here because we already saved the
|
||||
// prior value
|
||||
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
|
||||
prepareTest(test27, gTestRoot + "plugin_small.html");
|
||||
},
|
||||
"Test 26, expected to have a plugin notification bar");
|
||||
}
|
||||
|
||||
function test27() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 27: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null,
|
||||
() => {
|
||||
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
|
||||
prepareTest(test28, gTestRoot + "plugin_overlayed.html");
|
||||
},
|
||||
"Test 27, expected to not have a plugin notification bar");
|
||||
}
|
||||
|
||||
function test28() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 28: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test28b,
|
||||
"Test 28, expected the plugin infobar to be triggered when plugin was overlayed");
|
||||
}
|
||||
|
||||
function test28b()
|
||||
{
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let plugin = doc.getElementById("test");
|
||||
ok(plugin, "Test 28b, Found plugin in page");
|
||||
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 28b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
|
||||
ok(!plugin.activated, "Test 28b, Plugin should not be activated");
|
||||
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
|
||||
ok(!overlay.classList.contains("visible"), "Test 28b, Plugin overlay should be hidden");
|
||||
|
||||
prepareTest(test29, gTestRoot + "plugin_positioned.html");
|
||||
}
|
||||
|
||||
function test29() {
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins");
|
||||
ok(notification, "Test 29: There should be a plugin notification");
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
|
||||
waitForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") !== null,
|
||||
test29b,
|
||||
"Test 29, expected the plugin infobar to be triggered when plugin was overlayed");
|
||||
}
|
||||
|
||||
function test29b() {
|
||||
let doc = gTestBrowser.contentDocument;
|
||||
let plugin = doc.getElementById("test");
|
||||
ok(plugin, "Test 29b, Found plugin in page");
|
||||
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 29b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
|
||||
ok(!plugin.activated, "Test 29b, Plugin should not be activated");
|
||||
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
|
||||
ok(!overlay.classList.contains("visible"), "Test 29b, Plugin overlay should be hidden");
|
||||
|
||||
finishTest();
|
||||
}
|
||||
|
@ -301,18 +301,13 @@ let DocShellCapabilitiesListener = {
|
||||
_latestCapabilities: "",
|
||||
|
||||
init: function () {
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
gFrameTree.addObserver(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* onLocationChange() is called as soon as we start loading a page after
|
||||
* we are certain that there's nothing blocking the load (e.g. a content
|
||||
* policy added by AdBlock or the like).
|
||||
* onFrameTreeReset() is called as soon as we start loading a page.
|
||||
*/
|
||||
onLocationChange: function() {
|
||||
onFrameTreeReset: function() {
|
||||
// The order of docShell capabilities cannot change while we're running
|
||||
// so calling join() without sorting before is totally sufficient.
|
||||
let caps = DocShellCapabilities.collect(docShell).join(",");
|
||||
@ -322,15 +317,7 @@ let DocShellCapabilitiesListener = {
|
||||
this._latestCapabilities = caps;
|
||||
MessageQueue.push("disallow", () => caps || null);
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange: function () {},
|
||||
onProgressChange: function () {},
|
||||
onStatusChange: function () {},
|
||||
onSecurityChange: function () {},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -346,12 +333,12 @@ let SessionStorageListener = {
|
||||
init: function () {
|
||||
addEventListener("MozStorageChanged", this);
|
||||
Services.obs.addObserver(this, "browser:purge-domain-data", true);
|
||||
Services.obs.addObserver(this, "browser:purge-session-history", true);
|
||||
gFrameTree.addObserver(this);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
// Ignore events triggered by localStorage or globalStorage changes.
|
||||
if (isSessionStorageEvent(event)) {
|
||||
if (gFrameTree.contains(event.target) && isSessionStorageEvent(event)) {
|
||||
this.collect();
|
||||
}
|
||||
},
|
||||
@ -363,7 +350,17 @@ let SessionStorageListener = {
|
||||
},
|
||||
|
||||
collect: function () {
|
||||
MessageQueue.push("storage", () => SessionStorage.collect(docShell));
|
||||
if (docShell) {
|
||||
MessageQueue.push("storage", () => SessionStorage.collect(docShell, gFrameTree));
|
||||
}
|
||||
},
|
||||
|
||||
onFrameTreeCollected: function () {
|
||||
this.collect();
|
||||
},
|
||||
|
||||
onFrameTreeReset: function () {
|
||||
this.collect();
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
|
@ -11,7 +11,7 @@ const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
const EXPORTED_METHODS = ["addObserver", "contains", "map"];
|
||||
const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
|
||||
|
||||
/**
|
||||
* A FrameTree represents all frames that were reachable when the document
|
||||
@ -161,6 +161,34 @@ FrameTreeInternal.prototype = {
|
||||
return walk(this.content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given function |cb| to all frames stored in the tree. Use this
|
||||
* method if |map()| doesn't suit your needs and you want more control over
|
||||
* how data is collected.
|
||||
*
|
||||
* @param cb (function)
|
||||
* This callback receives the current frame as the only argument.
|
||||
*/
|
||||
forEach: function (cb) {
|
||||
let frames = this._frames;
|
||||
|
||||
function walk(frame) {
|
||||
cb(frame);
|
||||
|
||||
if (!frames.has(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.forEach(frame.frames, subframe => {
|
||||
if (frames.has(subframe)) {
|
||||
cb(subframe);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
walk(this.content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a given |frame| and its children in the frame tree.
|
||||
*
|
||||
|
@ -36,6 +36,8 @@ Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
|
||||
"resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
@ -102,7 +104,7 @@ const TaskUtils = {
|
||||
return promise.then(
|
||||
null,
|
||||
function onError(reason) {
|
||||
Cu.reportError("Uncaught asynchronous error: " + reason + " at\n" + reason.stack);
|
||||
console.error("Uncaught asynchronous error", reason, "at", reason.stack);
|
||||
throw reason;
|
||||
}
|
||||
);
|
||||
@ -179,8 +181,7 @@ let SessionFileInternal = {
|
||||
this._recordTelemetry(msg.telemetry);
|
||||
} catch (ex) {
|
||||
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
|
||||
Cu.reportError("Could not write session state file " + this.path
|
||||
+ ": " + ex);
|
||||
console.error("Could not write session state file ", this.path, ex);
|
||||
}
|
||||
|
||||
if (isFinalWrite) {
|
||||
@ -193,7 +194,7 @@ let SessionFileInternal = {
|
||||
SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
|
||||
this._recordTelemetry(msg.telemetry);
|
||||
return msg;
|
||||
}, Cu.reportError);
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
createBackupCopy: function (ext) {
|
||||
|
@ -17,7 +17,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
|
||||
"resource:///modules/sessionstore/SessionStore.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
|
||||
"resource:///modules/sessionstore/SessionFile.jsm");
|
||||
|
||||
@ -309,7 +310,7 @@ let SessionSaverInternal = {
|
||||
return SessionFile.write(data).then(() => {
|
||||
this.updateLastSaveTime();
|
||||
notify(null, "sessionstore-state-write-complete");
|
||||
}, Cu.reportError);
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -12,20 +12,29 @@ const Ci = Components.interfaces;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
|
||||
"resource:///modules/sessionstore/PrivacyLevel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
// Returns the principal for a given |frame| contained in a given |docShell|.
|
||||
function getPrincipalForFrame(docShell, frame) {
|
||||
let ssm = Services.scriptSecurityManager;
|
||||
let uri = frame.document.documentURIObject;
|
||||
return ssm.getDocShellCodebasePrincipal(uri, docShell);
|
||||
}
|
||||
|
||||
this.SessionStorage = Object.freeze({
|
||||
/**
|
||||
* Updates all sessionStorage "super cookies"
|
||||
* @param aDocShell
|
||||
* @param docShell
|
||||
* That tab's docshell (containing the sessionStorage)
|
||||
* @param frameTree
|
||||
* The docShell's FrameTree instance.
|
||||
* @return Returns a nested object that will have hosts as keys and per-host
|
||||
* session storage data as values. For example:
|
||||
* {"example.com": {"key": "value", "my_number": 123}}
|
||||
*/
|
||||
collect: function (aDocShell) {
|
||||
return SessionStorageInternal.collect(aDocShell);
|
||||
collect: function (docShell, frameTree) {
|
||||
return SessionStorageInternal.collect(docShell, frameTree);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -45,36 +54,40 @@ this.SessionStorage = Object.freeze({
|
||||
let SessionStorageInternal = {
|
||||
/**
|
||||
* Reads all session storage data from the given docShell.
|
||||
* @param aDocShell
|
||||
* @param docShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param frameTree
|
||||
* The docShell's FrameTree instance.
|
||||
* @return Returns a nested object that will have hosts as keys and per-host
|
||||
* session storage data as values. For example:
|
||||
* {"example.com": {"key": "value", "my_number": 123}}
|
||||
*/
|
||||
collect: function (aDocShell) {
|
||||
collect: function (docShell, frameTree) {
|
||||
let data = {};
|
||||
let webNavigation = aDocShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let shistory = webNavigation.sessionHistory;
|
||||
let visitedOrigins = new Set();
|
||||
|
||||
for (let i = 0; shistory && i < shistory.count; i++) {
|
||||
let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
|
||||
frameTree.forEach(frame => {
|
||||
let principal = getPrincipalForFrame(docShell, frame);
|
||||
if (!principal) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the root domain of the current history entry
|
||||
// and use that as a key for the per-host storage data.
|
||||
let origin = principal.jarPrefix + principal.origin;
|
||||
if (data.hasOwnProperty(origin)) {
|
||||
if (visitedOrigins.has(origin)) {
|
||||
// Don't read a host twice.
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
let originData = this._readEntry(principal, aDocShell);
|
||||
// Mark the current origin as visited.
|
||||
visitedOrigins.add(origin);
|
||||
|
||||
let originData = this._readEntry(principal, docShell);
|
||||
if (Object.keys(originData).length) {
|
||||
data[origin] = originData;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(data).length ? data : null;
|
||||
},
|
||||
@ -89,10 +102,11 @@ let SessionStorageInternal = {
|
||||
* {"example.com": {"key": "value", "my_number": 123}}
|
||||
*/
|
||||
restore: function (aDocShell, aStorageData) {
|
||||
for (let [host, data] in Iterator(aStorageData)) {
|
||||
for (let host of Object.keys(aStorageData)) {
|
||||
let data = aStorageData[host];
|
||||
let uri = Services.io.newURI(host, null, null);
|
||||
let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
|
||||
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
|
||||
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
|
||||
|
||||
// There is no need to pass documentURI, it's only used to fill documentURI property of
|
||||
// domstorage event, which in this case has no consumer. Prevention of events in case
|
||||
@ -104,7 +118,7 @@ let SessionStorageInternal = {
|
||||
storage.setItem(key, value);
|
||||
} catch (e) {
|
||||
// throws e.g. for URIs that can't have sessionStorage
|
||||
Cu.reportError(e);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,7 +136,7 @@ let SessionStorageInternal = {
|
||||
let storage;
|
||||
|
||||
try {
|
||||
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
|
||||
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
|
||||
storage = storageManager.getStorage(aPrincipal);
|
||||
} catch (e) {
|
||||
// sessionStorage might throw if it's turned off, see bug 458954
|
||||
@ -142,25 +156,3 @@ let SessionStorageInternal = {
|
||||
return hostData;
|
||||
}
|
||||
};
|
||||
|
||||
let History = {
|
||||
/**
|
||||
* Returns a given history entry's URI.
|
||||
* @param aHistory
|
||||
* That tab's session history
|
||||
* @param aIndex
|
||||
* The history entry's index
|
||||
* @param aDocShell
|
||||
* That tab's docshell
|
||||
*/
|
||||
getPrincipalForEntry: function History_getPrincipalForEntry(aHistory,
|
||||
aIndex,
|
||||
aDocShell) {
|
||||
try {
|
||||
return Services.scriptSecurityManager.getDocShellCodebasePrincipal(
|
||||
aHistory.getEntryAtIndex(aIndex, false).URI, aDocShell);
|
||||
} catch (e) {
|
||||
// This might throw for some reason.
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -102,6 +102,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
||||
"@mozilla.org/base/telemetry;1", "nsITelemetry");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
|
||||
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
|
||||
@ -959,7 +961,7 @@ let SessionStoreInternal = {
|
||||
// Let everyone know we're done.
|
||||
this._deferredInitialized.resolve();
|
||||
}
|
||||
}, Cu.reportError);
|
||||
}, console.error);
|
||||
};
|
||||
|
||||
aWindow.addEventListener("load", onload);
|
||||
@ -3136,7 +3138,7 @@ let SessionStoreInternal = {
|
||||
cookie.value, !!cookie.secure, !!cookie.httponly, true,
|
||||
"expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
|
||||
}
|
||||
catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
|
||||
catch (ex) { console.error(ex); } // don't let a single cookie stop recovering
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
|
||||
"resource:///modules/sessionstore/Messenger.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
|
||||
@ -328,7 +330,7 @@ let TabStateInternal = {
|
||||
history = syncHandler.collectSessionHistory(includePrivateData);
|
||||
} catch (e) {
|
||||
// This may happen if the tab has crashed.
|
||||
Cu.reportError(e);
|
||||
console.error(e);
|
||||
return tabData;
|
||||
}
|
||||
|
||||
@ -364,7 +366,7 @@ let TabStateInternal = {
|
||||
if (key != "storage" || includePrivateData) {
|
||||
tabData[key] = data[key];
|
||||
} else {
|
||||
tabData.storage = {};
|
||||
let storage = {};
|
||||
let isPinned = tab.pinned;
|
||||
|
||||
// If we're not allowed to include private data, let's filter out hosts
|
||||
@ -372,9 +374,13 @@ let TabStateInternal = {
|
||||
for (let host of Object.keys(data.storage)) {
|
||||
let isHttps = host.startsWith("https:");
|
||||
if (PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
|
||||
tabData.storage[host] = data.storage[host];
|
||||
storage[host] = data.storage[host];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(storage).length) {
|
||||
tabData.storage = storage;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -43,6 +43,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
|
||||
"resource:///modules/sessionstore/SessionFile.jsm");
|
||||
|
||||
@ -85,7 +87,7 @@ SessionStartup.prototype = {
|
||||
|
||||
SessionFile.read().then(
|
||||
this._onSessionFileRead.bind(this),
|
||||
Cu.reportError
|
||||
console.error
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -20,6 +20,7 @@ support-files =
|
||||
browser_pageStyle_sample_nested.html
|
||||
browser_scrollPositions_sample.html
|
||||
browser_scrollPositions_sample_frameset.html
|
||||
browser_sessionStorage.html
|
||||
browser_248970_b_sample.html
|
||||
browser_339445_sample.html
|
||||
browser_346337_sample.html
|
||||
|
@ -59,12 +59,12 @@ add_task(function flush_on_duplicate() {
|
||||
"sessionStorage data has been flushed when duplicating tabs");
|
||||
|
||||
yield promiseTabRestored(tab2);
|
||||
let {storage} = JSON.parse(ss.getTabState(tab2));
|
||||
gBrowser.removeTab(tab2)
|
||||
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
|
||||
is(storage["http://example.com"].test, "on-duplicate",
|
||||
"sessionStorage data has been flushed when duplicating tabs");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
gBrowser.removeTab(tab2);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>browser_sessionStorage.html</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript;version=1.8">
|
||||
let isOuter = window == window.top;
|
||||
let args = window.location.search.slice(1).split("&");
|
||||
let rand = args[0];
|
||||
|
||||
if (isOuter) {
|
||||
let iframe = document.createElement("iframe");
|
||||
let isSecure = args.indexOf("secure") > -1;
|
||||
let scheme = isSecure ? "https" : "http";
|
||||
iframe.setAttribute("src", scheme + "://example.com" + location.pathname + "?" + rand);
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
|
||||
if (sessionStorage.length === 0) {
|
||||
sessionStorage.test = (isOuter ? "outer" : "inner") + "-value-" + rand;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -3,35 +3,39 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/Promise.jsm", tmp);
|
||||
let {Promise} = tmp;
|
||||
const RAND = Math.random();
|
||||
const URL = "http://mochi.test:8888/browser/" +
|
||||
"browser/components/sessionstore/test/browser_sessionStorage.html" +
|
||||
"?" + RAND;
|
||||
|
||||
const INITIAL_VALUE = "initial-value-" + Date.now();
|
||||
const OUTER_VALUE = "outer-value-" + RAND;
|
||||
const INNER_VALUE = "inner-value-" + RAND;
|
||||
|
||||
/**
|
||||
* This test ensures that setting, modifying and restoring sessionStorage data
|
||||
* works as expected.
|
||||
*/
|
||||
add_task(function session_storage() {
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
|
||||
let tab = gBrowser.addTab(URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Flush to make sure chrome received all data.
|
||||
SyncHandlers.get(browser).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://example.com"].test, INNER_VALUE,
|
||||
"sessionStorage data for example.com has been serialized correctly");
|
||||
is(storage["http://mochi.test:8888"].test, INITIAL_VALUE,
|
||||
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
|
||||
"sessionStorage data for mochi.test has been serialized correctly");
|
||||
|
||||
// Ensure that modifying sessionStore values works.
|
||||
yield modifySessionStorage(browser, {test: "modified"});
|
||||
yield modifySessionStorage2(browser, {test: "modified2"});
|
||||
SyncHandlers.get(browser).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://example.com"].test, "modified2",
|
||||
"sessionStorage data for example.com has been serialized correctly");
|
||||
is(storage["http://mochi.test:8888"].test, "modified",
|
||||
"sessionStorage data for mochi.test has been serialized correctly");
|
||||
@ -45,22 +49,40 @@ add_task(function session_storage() {
|
||||
SyncHandlers.get(browser2).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab2));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://example.com"].test, "modified2",
|
||||
"sessionStorage data for example.com has been duplicated correctly");
|
||||
is(storage["http://mochi.test:8888"].test, "modified",
|
||||
"sessionStorage data for mochi.test has been duplicated correctly");
|
||||
|
||||
// Ensure that the content script retains restored data
|
||||
// (by e.g. duplicateTab) and send it along with new data.
|
||||
yield modifySessionStorage(browser2, {test: "modified2"});
|
||||
// (by e.g. duplicateTab) and sends it along with new data.
|
||||
yield modifySessionStorage(browser2, {test: "modified3"});
|
||||
SyncHandlers.get(browser2).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab2));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://example.com"].test, "modified2",
|
||||
"sessionStorage data for example.com has been duplicated correctly");
|
||||
is(storage["http://mochi.test:8888"].test, "modified2",
|
||||
is(storage["http://mochi.test:8888"].test, "modified3",
|
||||
"sessionStorage data for mochi.test has been duplicated correctly");
|
||||
|
||||
// Check that loading a new URL discards data.
|
||||
browser2.loadURI("http://mochi.test:8888/");
|
||||
yield promiseBrowserLoaded(browser2);
|
||||
SyncHandlers.get(browser2).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab2));
|
||||
is(storage["http://mochi.test:8888"].test, "modified3",
|
||||
"navigating retains correct storage data");
|
||||
ok(!storage["http://example.com"], "storage data was discarded");
|
||||
|
||||
// Check that loading a new URL discards data.
|
||||
browser2.loadURI("about:mozilla");
|
||||
yield promiseBrowserLoaded(browser2);
|
||||
SyncHandlers.get(browser2).flush();
|
||||
|
||||
let state = JSON.parse(ss.getTabState(tab2));
|
||||
ok(!state.hasOwnProperty("storage"), "storage data was discarded");
|
||||
|
||||
// Clean up.
|
||||
gBrowser.removeTab(tab);
|
||||
gBrowser.removeTab(tab2);
|
||||
@ -71,10 +93,12 @@ add_task(function session_storage() {
|
||||
* sessionStorage data collected for tabs.
|
||||
*/
|
||||
add_task(function purge_domain() {
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
|
||||
let tab = gBrowser.addTab(URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
yield notifyObservers(browser, "browser:purge-domain-data", "mochi.test");
|
||||
// Purge data for "mochi.test".
|
||||
yield purgeDomainData(browser, "mochi.test");
|
||||
|
||||
// Flush to make sure chrome received all data.
|
||||
SyncHandlers.get(browser).flush();
|
||||
@ -82,56 +106,36 @@ add_task(function purge_domain() {
|
||||
let {storage} = JSON.parse(ss.getTabState(tab));
|
||||
ok(!storage["http://mochi.test:8888"],
|
||||
"sessionStorage data for mochi.test has been purged");
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://example.com"].test, INNER_VALUE,
|
||||
"sessionStorage data for example.com has been preserved");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* This test ensures that purging session history data also purges data from
|
||||
* sessionStorage data collected for tabs
|
||||
*/
|
||||
add_task(function purge_shistory() {
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
yield notifyObservers(browser, "browser:purge-session-history");
|
||||
|
||||
// Flush to make sure chrome received all data.
|
||||
SyncHandlers.get(browser).flush();
|
||||
|
||||
let {storage} = JSON.parse(ss.getTabState(tab));
|
||||
ok(!storage["http://example.com"],
|
||||
"sessionStorage data for example.com has been purged");
|
||||
is(storage["http://mochi.test:8888"].test, INITIAL_VALUE,
|
||||
"sessionStorage data for mochi.test has been preserved");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* This test ensures that collecting sessionStorage data respects the privacy
|
||||
* levels as set by the user.
|
||||
*/
|
||||
add_task(function respect_privacy_level() {
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
|
||||
let tab = gBrowser.addTab(URL + "&secure");
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
|
||||
"http sessionStorage data has been saved");
|
||||
is(storage["https://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["https://example.com"].test, INNER_VALUE,
|
||||
"https sessionStorage data has been saved");
|
||||
|
||||
// Disable saving data for encrypted sites.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
|
||||
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
|
||||
let tab = gBrowser.addTab(URL + "&secure");
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
|
||||
"http sessionStorage data has been saved");
|
||||
ok(!storage["https://example.com"],
|
||||
"https sessionStorage data has *not* been saved");
|
||||
@ -140,17 +144,15 @@ add_task(function respect_privacy_level() {
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
|
||||
|
||||
// Check that duplicating a tab copies all private data.
|
||||
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
|
||||
let tab = gBrowser.addTab(URL + "&secure");
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
let tab2 = gBrowser.duplicateTab(tab);
|
||||
yield promiseBrowserLoaded(tab2.linkedBrowser);
|
||||
yield promiseTabRestored(tab2);
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
// With privacy_level=2 the |tab| shouldn't have any sessionStorage data.
|
||||
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
|
||||
ok(!storage["http://example.com"],
|
||||
"http sessionStorage data has *not* been saved");
|
||||
ok(!storage["https://example.com"],
|
||||
"https sessionStorage data has *not* been saved");
|
||||
ok(!storage, "sessionStorage data has *not* been saved");
|
||||
|
||||
// Restore the default privacy level and close the duplicated tab.
|
||||
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
|
||||
@ -158,42 +160,26 @@ add_task(function respect_privacy_level() {
|
||||
|
||||
// With privacy_level=0 the duplicated |tab2| should persist all data.
|
||||
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
|
||||
is(storage["http://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
|
||||
"http sessionStorage data has been saved");
|
||||
is(storage["https://example.com"].test, INITIAL_VALUE,
|
||||
is(storage["https://example.com"].test, INNER_VALUE,
|
||||
"https sessionStorage data has been saved");
|
||||
});
|
||||
|
||||
function createTabWithStorageData(urls) {
|
||||
return Task.spawn(function task() {
|
||||
let tab = gBrowser.addTab();
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
for (let url of urls) {
|
||||
browser.loadURI(url);
|
||||
yield promiseBrowserLoaded(browser);
|
||||
yield modifySessionStorage(browser, {test: INITIAL_VALUE});
|
||||
}
|
||||
|
||||
throw new Task.Result(tab);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForStorageEvent(browser) {
|
||||
return promiseContentMessage(browser, "ss-test:MozStorageChanged");
|
||||
}
|
||||
|
||||
function waitForUpdateMessage(browser) {
|
||||
return promiseContentMessage(browser, "SessionStore:update");
|
||||
}
|
||||
|
||||
function modifySessionStorage(browser, data) {
|
||||
browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage", data);
|
||||
return waitForStorageEvent(browser);
|
||||
}
|
||||
|
||||
function notifyObservers(browser, topic, data) {
|
||||
let msg = {topic: topic, data: data};
|
||||
browser.messageManager.sendAsyncMessage("ss-test:notifyObservers", msg);
|
||||
return waitForUpdateMessage(browser);
|
||||
function modifySessionStorage2(browser, data) {
|
||||
browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage2", data);
|
||||
return waitForStorageEvent(browser);
|
||||
}
|
||||
|
||||
function purgeDomainData(browser, domain) {
|
||||
return sendMessage(browser, "ss-test:purgeDomainData", domain);
|
||||
}
|
||||
|
@ -33,8 +33,15 @@ addMessageListener("ss-test:modifySessionStorage", function (msg) {
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:notifyObservers", function ({data: {topic, data}}) {
|
||||
Services.obs.notifyObservers(null, topic, data || "");
|
||||
addMessageListener("ss-test:modifySessionStorage2", function (msg) {
|
||||
for (let key of Object.keys(msg.data)) {
|
||||
content.frames[0].sessionStorage[key] = msg.data[key];
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
|
||||
Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
|
||||
content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:getStyleSheets", function (msg) {
|
||||
|
@ -61,44 +61,59 @@ ManifestEditor.prototype = {
|
||||
return this.update();
|
||||
},
|
||||
|
||||
_onEval: function(evalString) {
|
||||
let manifest = this.manifest;
|
||||
eval("manifest" + evalString);
|
||||
_onEval: function(variable, value) {
|
||||
let parent = this._descend(variable.ownerView.symbolicPath);
|
||||
try {
|
||||
parent[variable.name] = JSON.parse(value);
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
_onSwitch: function(variable, newName) {
|
||||
let manifest = this.manifest;
|
||||
let newSymbolicName = variable.ownerView.symbolicName +
|
||||
"['" + newName + "']";
|
||||
if (newSymbolicName == variable.symbolicName) {
|
||||
if (variable.name == newName) {
|
||||
return;
|
||||
}
|
||||
|
||||
let evalString = "manifest" + newSymbolicName + " = " +
|
||||
"manifest" + variable.symbolicName + ";" +
|
||||
"delete manifest" + variable.symbolicName;
|
||||
let parent = this._descend(variable.ownerView.symbolicPath);
|
||||
parent[newName] = parent[variable.name];
|
||||
delete parent[variable.name];
|
||||
|
||||
eval(evalString);
|
||||
this.update();
|
||||
},
|
||||
|
||||
_onDelete: function(variable) {
|
||||
let manifest = this.manifest;
|
||||
let evalString = "delete manifest" + variable.symbolicName;
|
||||
eval(evalString);
|
||||
let parent = this._descend(variable.ownerView.symbolicPath);
|
||||
delete parent[variable.name];
|
||||
},
|
||||
|
||||
_onNew: function(variable, newName, newValue) {
|
||||
let manifest = this.manifest;
|
||||
let symbolicName = variable.symbolicName + "['" + newName + "']";
|
||||
let evalString = "manifest" + symbolicName + " = " + newValue + ";";
|
||||
eval(evalString);
|
||||
let parent = this._descend(variable.symbolicPath);
|
||||
try {
|
||||
parent[newName] = JSON.parse(newValue);
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value located at a given path in the manifest.
|
||||
* @param path array
|
||||
* A string for each path component: ["developer", "name"]
|
||||
*/
|
||||
_descend: function(path) {
|
||||
let parent = this.manifest;
|
||||
while (path.length) {
|
||||
parent = parent[path.shift()];
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
update: function() {
|
||||
this.editor.createHierarchy();
|
||||
this.editor.rawObject = this.manifest;
|
||||
this.editor.commitHierarchy();
|
||||
|
||||
|
@ -6,7 +6,6 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
|
||||
|
||||
|
||||
let gManifestWindow, gManifestEditor;
|
||||
|
||||
function test() {
|
||||
@ -22,7 +21,9 @@ function test() {
|
||||
gManifestWindow = getManifestWindow();
|
||||
gManifestEditor = getProjectsWindow().UI.manifestEditor;
|
||||
yield changeManifestValue("name", "the best app");
|
||||
yield changeManifestValueBad("name", "the worst app");
|
||||
yield addNewManifestProperty("developer", "foo", "bar");
|
||||
yield addNewManifestPropertyBad("developer", "blob", "bob");
|
||||
gManifestWindow = null;
|
||||
gManifestEditor = null;
|
||||
|
||||
@ -64,6 +65,33 @@ function changeManifestValue(key, value) {
|
||||
});
|
||||
}
|
||||
|
||||
function changeManifestValueBad(key, value) {
|
||||
return Task.spawn(function() {
|
||||
let propElem = gManifestWindow.document
|
||||
.querySelector("[id ^= '" + key + "']");
|
||||
is(propElem.querySelector(".name").value, key,
|
||||
"Key doesn't match expected value");
|
||||
|
||||
let valueElem = propElem.querySelector(".value");
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, gManifestWindow);
|
||||
|
||||
let valueInput = propElem.querySelector(".element-value-input");
|
||||
// Leaving out quotes will result in an error, so no change should be made.
|
||||
valueInput.value = value;
|
||||
EventUtils.sendKey("RETURN", gManifestWindow);
|
||||
|
||||
yield waitForUpdate();
|
||||
// Elements have all been replaced, re-select them
|
||||
propElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
|
||||
valueElem = propElem.querySelector(".value");
|
||||
isnot(valueElem.value, '"' + value + '"',
|
||||
"Value was changed, but it should not have been");
|
||||
|
||||
isnot(gManifestEditor.manifest[key], value,
|
||||
"Manifest was changed, but it should not have been");
|
||||
});
|
||||
}
|
||||
|
||||
function addNewManifestProperty(parent, key, value) {
|
||||
return Task.spawn(function() {
|
||||
let parentElem = gManifestWindow.document
|
||||
@ -103,3 +131,34 @@ function addNewManifestProperty(parent, key, value) {
|
||||
"Manifest doesn't contain expected value");
|
||||
});
|
||||
}
|
||||
|
||||
function addNewManifestPropertyBad(parent, key, value) {
|
||||
return Task.spawn(function() {
|
||||
let parentElem = gManifestWindow.document
|
||||
.querySelector("[id ^= '" + parent + "']");
|
||||
ok(parentElem,
|
||||
"Found parent element");
|
||||
let addPropertyElem = parentElem
|
||||
.querySelector(".variables-view-add-property");
|
||||
ok(addPropertyElem,
|
||||
"Found add-property button");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, addPropertyElem, gManifestWindow);
|
||||
|
||||
let nameInput = parentElem.querySelector(".element-name-input");
|
||||
nameInput.value = key;
|
||||
EventUtils.sendKey("TAB", gManifestWindow);
|
||||
|
||||
let valueInput = parentElem.querySelector(".element-value-input");
|
||||
// Leaving out quotes will result in an error, so no change should be made.
|
||||
valueInput.value = value;
|
||||
EventUtils.sendKey("RETURN", gManifestWindow);
|
||||
|
||||
yield waitForUpdate();
|
||||
|
||||
let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
|
||||
ok(!newElem, "Key was added, but it should not have been");
|
||||
ok(!(key in gManifestEditor.manifest[parent]),
|
||||
"Manifest contains key, but it should not");
|
||||
});
|
||||
}
|
||||
|
@ -841,7 +841,6 @@ StackFrames.prototype = {
|
||||
|
||||
// Start recording any added variables or properties in any scope and
|
||||
// clear existing scopes to create each one dynamically.
|
||||
DebuggerView.Variables.createHierarchy();
|
||||
DebuggerView.Variables.empty();
|
||||
|
||||
// If watch expressions evaluation results are available, create a scope
|
||||
@ -1540,6 +1539,7 @@ Tracer.prototype = {
|
||||
location: location,
|
||||
id: this._idCounter++
|
||||
};
|
||||
|
||||
this._stack.push(item);
|
||||
DebuggerView.Tracer.addTrace({
|
||||
type: "call",
|
||||
@ -1598,7 +1598,7 @@ Tracer.prototype = {
|
||||
getOwnPropertyNames: (callback) => {
|
||||
callback({
|
||||
ownPropertyNames: aObject.ownProperties
|
||||
? Object.keys(aObject.ownProperties)
|
||||
? Object.keys(aObject.ownProperties)
|
||||
: []
|
||||
});
|
||||
},
|
||||
|
@ -1088,8 +1088,10 @@ function TracerView() {
|
||||
DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
|
||||
this._onStartTracing =
|
||||
DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
|
||||
this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this));
|
||||
this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this));
|
||||
this._onClear =
|
||||
DevToolsUtils.makeInfallible(this._onClear.bind(this));
|
||||
this._onSelect =
|
||||
DevToolsUtils.makeInfallible(this._onSelect.bind(this));
|
||||
this._onMouseOver =
|
||||
DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
|
||||
this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this));
|
||||
@ -1118,13 +1120,10 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
|
||||
this.widget = new FastListWidget(document.getElementById("tracer-traces"));
|
||||
|
||||
this._traceButton.removeAttribute("hidden");
|
||||
this._tracerTab.removeAttribute("hidden");
|
||||
|
||||
this._tracerDeck = document.getElementById("tracer-deck");
|
||||
this._search = document.getElementById("tracer-search");
|
||||
|
||||
this._template = document.getElementsByClassName("trace-item-template")[0];
|
||||
this._templateItem = this._template.getElementsByClassName("trace-item")[0];
|
||||
this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
|
||||
@ -1133,12 +1132,15 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("mouseover", this._onMouseOver, false);
|
||||
this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
|
||||
|
||||
this._search.addEventListener("input", this._onSearch, false);
|
||||
|
||||
this._startTooltip = L10N.getStr("startTracingTooltip");
|
||||
this._stopTooltip = L10N.getStr("stopTracingTooltip");
|
||||
this._tracingNotStartedString = L10N.getStr("tracingNotStartedText");
|
||||
this._noFunctionCallsString = L10N.getStr("noFunctionCallsText");
|
||||
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
this.emptyText = this._tracingNotStartedString;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1171,23 +1173,38 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Function invoked either by the "startTracing" command or by
|
||||
* _onToggleTracing to start execution tracing in the backend.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved once the tracing has successfully started.
|
||||
*/
|
||||
_onStartTracing: function() {
|
||||
this._tracerDeck.selectedIndex = 0;
|
||||
this._traceButton.setAttribute("checked", true);
|
||||
this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
|
||||
|
||||
this.empty();
|
||||
DebuggerController.Tracer.startTracing();
|
||||
this.emptyText = this._noFunctionCallsString;
|
||||
|
||||
let deferred = promise.defer();
|
||||
DebuggerController.Tracer.startTracing(deferred.resolve);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by _onToggleTracing to stop execution tracing in the
|
||||
* backend.
|
||||
*
|
||||
* @return object
|
||||
* A promise resolved once the tracing has successfully stopped.
|
||||
*/
|
||||
_onStopTracing: function() {
|
||||
this._traceButton.removeAttribute("checked");
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
DebuggerController.Tracer.stopTracing();
|
||||
|
||||
this.emptyText = this._tracingNotStartedString;
|
||||
|
||||
let deferred = promise.defer();
|
||||
DebuggerController.Tracer.stopTracing(deferred.resolve);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1336,7 +1353,6 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
selectTab: function() {
|
||||
const tabs = this._tracerTab.parentElement;
|
||||
tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
|
||||
this._tracerDeck.selectedIndex = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1856,8 +1872,9 @@ VariableBubbleView.prototype = {
|
||||
this._tooltip.setVariableContent(objectActor, {
|
||||
searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
|
||||
searchEnabled: Prefs.variablesSearchboxVisible,
|
||||
eval: aString => {
|
||||
DebuggerController.StackFrames.evaluate(aString);
|
||||
eval: (variable, value) => {
|
||||
let string = variable.evaluationMacro(variable, value);
|
||||
DebuggerController.StackFrames.evaluate(string);
|
||||
DebuggerView.VariableBubble.hideContents();
|
||||
}
|
||||
}, {
|
||||
|
@ -162,7 +162,10 @@ let DebuggerView = {
|
||||
emptyText: L10N.getStr("emptyVariablesText"),
|
||||
onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
|
||||
searchEnabled: Prefs.variablesSearchboxVisible,
|
||||
eval: DebuggerController.StackFrames.evaluate,
|
||||
eval: (variable, value) => {
|
||||
let string = variable.evaluationMacro(variable, value);
|
||||
DebuggerController.StackFrames.evaluate(string);
|
||||
},
|
||||
lazyEmpty: true
|
||||
});
|
||||
|
||||
|
@ -372,36 +372,25 @@
|
||||
<tabpanel id="callstack-tabpanel">
|
||||
<vbox id="callstack-list" flex="1"/>
|
||||
</tabpanel>
|
||||
<tabpanel id="tracer-tabpanel" flex="1">
|
||||
<deck id="tracer-deck" selectedIndex="1" flex="1">
|
||||
<vbox flex="1">
|
||||
<vbox id="tracer-traces" flex="1">
|
||||
<hbox class="trace-item-template" hidden="true">
|
||||
<hbox class="trace-item" align="center" flex="1" crop="end">
|
||||
<label class="trace-type plain"/>
|
||||
<label class="trace-name plain" crop="end"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<toolbar id="tracer-toolbar" class="devtools-toolbar">
|
||||
<toolbarbutton id="clear-tracer"
|
||||
label="&debuggerUI.clearButton;"
|
||||
tooltiptext="&debuggerUI.clearButton.tooltip;"
|
||||
command="clearTraces"
|
||||
class="devtools-toolbarbutton"/>
|
||||
<textbox id="tracer-search"
|
||||
class="devtools-searchinput"
|
||||
flex="1"
|
||||
type="search"/>
|
||||
</toolbar>
|
||||
</vbox>
|
||||
<vbox id="tracer-message"
|
||||
flex="1"
|
||||
align="center"
|
||||
pack="center">
|
||||
<description value="&debuggerUI.tracingNotStarted.label;" />
|
||||
</vbox>
|
||||
</deck>
|
||||
<tabpanel id="tracer-tabpanel">
|
||||
<vbox id="tracer-traces" flex="1"/>
|
||||
<hbox class="trace-item-template" hidden="true">
|
||||
<hbox class="trace-item" align="center" flex="1" crop="end">
|
||||
<label class="trace-type plain"/>
|
||||
<label class="trace-name plain" crop="end"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<toolbar id="tracer-toolbar" class="devtools-toolbar">
|
||||
<toolbarbutton id="clear-tracer"
|
||||
label="&debuggerUI.clearButton;"
|
||||
tooltiptext="&debuggerUI.clearButton.tooltip;"
|
||||
command="clearTraces"
|
||||
class="devtools-toolbarbutton"/>
|
||||
<textbox id="tracer-search"
|
||||
class="devtools-searchinput"
|
||||
flex="1"
|
||||
type="search"/>
|
||||
</toolbar>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
|
@ -203,6 +203,7 @@ support-files =
|
||||
[browser_dbg_tracing-02.js]
|
||||
[browser_dbg_tracing-03.js]
|
||||
[browser_dbg_tracing-04.js]
|
||||
[browser_dbg_tracing-05.js]
|
||||
[browser_dbg_variables-view-01.js]
|
||||
[browser_dbg_variables-view-02.js]
|
||||
[browser_dbg_variables-view-03.js]
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger, gVariables;
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
@ -16,7 +16,6 @@ function test() {
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gVariables = gDebugger.DebuggerView.Variables;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(() => startTracing(gPanel))
|
||||
@ -82,5 +81,4 @@ registerCleanupFunction(function() {
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gVariables = null;
|
||||
});
|
||||
|
85
browser/devtools/debugger/test/browser_dbg_tracing-05.js
Normal file
85
browser/devtools/debugger/test/browser_dbg_tracing-05.js
Normal file
@ -0,0 +1,85 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that text describing the tracing state is correctly displayed.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTracer, gL10N;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gTracer = gDebugger.DebuggerView.Tracer;
|
||||
gL10N = gDebugger.L10N;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(testTracingNotStartedText)
|
||||
.then(() => gTracer._onStartTracing())
|
||||
.then(testFunctionCallsUnavailableText)
|
||||
.then(clickButton)
|
||||
.then(() => waitForClientEvents(aPanel, "traces"))
|
||||
.then(testNoEmptyText)
|
||||
.then(() => gTracer._onClear())
|
||||
.then(testFunctionCallsUnavailableText)
|
||||
.then(() => gTracer._onStopTracing())
|
||||
.then(testTracingNotStartedText)
|
||||
.then(() => gTracer._onClear())
|
||||
.then(testTracingNotStartedText)
|
||||
.then(() => {
|
||||
const deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
DevToolsUtils.reportException("browser_dbg_tracing-05.js", aError);
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testTracingNotStartedText() {
|
||||
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
|
||||
ok(label,
|
||||
"A label is displayed in the tracer tabpanel.");
|
||||
is(label.getAttribute("value"), gL10N.getStr("tracingNotStartedText"),
|
||||
"The correct {{tracingNotStartedText}} is displayed in the tracer tabpanel.");
|
||||
}
|
||||
|
||||
function testFunctionCallsUnavailableText() {
|
||||
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
|
||||
ok(label,
|
||||
"A label is displayed in the tracer tabpanel.");
|
||||
is(label.getAttribute("value"), gL10N.getStr("noFunctionCallsText"),
|
||||
"The correct {{noFunctionCallsText}} is displayed in the tracer tabpanel.");
|
||||
}
|
||||
|
||||
function testNoEmptyText() {
|
||||
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
|
||||
ok(!label,
|
||||
"No label should be displayed in the tracer tabpanel.");
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gTracer = null;
|
||||
gL10N = null;
|
||||
});
|
@ -45,6 +45,7 @@ const FastListWidget = module.exports = function FastListWidget(aNode) {
|
||||
|
||||
// Delegate some of the associated node's methods to satisfy the interface
|
||||
// required by MenuContainer instances.
|
||||
ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
|
||||
ViewHelpers.delegateWidgetEventMethods(this, aNode);
|
||||
}
|
||||
|
||||
@ -152,18 +153,6 @@ FastListWidget.prototype = {
|
||||
return this._orderedMenuElementsArray[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value of the named attribute on this container.
|
||||
*
|
||||
* @param string name
|
||||
* The name of the attribute.
|
||||
* @return string
|
||||
* The current attribute value.
|
||||
*/
|
||||
getAttribute: function(name) {
|
||||
return this._parent.getAttribute(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new attribute or changes an existing attribute on this container.
|
||||
*
|
||||
@ -174,6 +163,10 @@ FastListWidget.prototype = {
|
||||
*/
|
||||
setAttribute: function(name, value) {
|
||||
this._parent.setAttribute(name, value);
|
||||
|
||||
if (name == "emptyText") {
|
||||
this._textWhenEmpty = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -184,6 +177,10 @@ FastListWidget.prototype = {
|
||||
*/
|
||||
removeAttribute: function(name) {
|
||||
this._parent.removeAttribute(name);
|
||||
|
||||
if (name == "emptyText") {
|
||||
this._removeEmptyText();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -203,11 +200,51 @@ FastListWidget.prototype = {
|
||||
boxObject.scrollBy(-this._list.clientWidth, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the text displayed in this container when empty.
|
||||
* @param string aValue
|
||||
*/
|
||||
set _textWhenEmpty(aValue) {
|
||||
if (this._emptyTextNode) {
|
||||
this._emptyTextNode.setAttribute("value", aValue);
|
||||
}
|
||||
this._emptyTextValue = aValue;
|
||||
this._showEmptyText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and appends a label signaling that this container is empty.
|
||||
*/
|
||||
_showEmptyText: function() {
|
||||
if (this._emptyTextNode || !this._emptyTextValue) {
|
||||
return;
|
||||
}
|
||||
let label = this.document.createElement("label");
|
||||
label.className = "plain fast-list-widget-empty-text";
|
||||
label.setAttribute("value", this._emptyTextValue);
|
||||
|
||||
this._parent.insertBefore(label, this._list);
|
||||
this._emptyTextNode = label;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the label signaling that this container is empty.
|
||||
*/
|
||||
_removeEmptyText: function() {
|
||||
if (!this._emptyTextNode) {
|
||||
return;
|
||||
}
|
||||
this._parent.removeChild(this._emptyTextNode);
|
||||
this._emptyTextNode = null;
|
||||
},
|
||||
|
||||
window: null,
|
||||
document: null,
|
||||
_parent: null,
|
||||
_list: null,
|
||||
_selectedItem: null,
|
||||
_orderedMenuElementsArray: null,
|
||||
_itemsByElement: null
|
||||
_itemsByElement: null,
|
||||
_emptyTextNode: null,
|
||||
_emptyTextValue: ""
|
||||
};
|
||||
|
@ -295,7 +295,7 @@ SideMenuWidget.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the text displayed in this container as a when empty.
|
||||
* Sets the text displayed in this container when empty.
|
||||
* @param string aValue
|
||||
*/
|
||||
set _textWhenEmpty(aValue) {
|
||||
|
@ -189,7 +189,7 @@ SimpleListWidget.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the text displayed in this container as a when empty.
|
||||
* Sets the text displayed in this container when empty.
|
||||
* @param string aValue
|
||||
*/
|
||||
set _textWhenEmpty(aValue) {
|
||||
|
@ -154,21 +154,22 @@ VariablesView.prototype = {
|
||||
if (!this._store.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._store.length = 0;
|
||||
this._itemsByElement.clear();
|
||||
this._prevHierarchy = this._currHierarchy;
|
||||
this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.
|
||||
|
||||
// Check if this empty operation may be executed lazily.
|
||||
if (this.lazyEmpty && aTimeout > 0) {
|
||||
this._emptySoon(aTimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
let list = this._list;
|
||||
|
||||
while (list.hasChildNodes()) {
|
||||
list.firstChild.remove();
|
||||
while (this._list.hasChildNodes()) {
|
||||
this._list.firstChild.remove();
|
||||
}
|
||||
|
||||
this._store.length = 0;
|
||||
this._itemsByElement.clear();
|
||||
|
||||
this._appendEmptyNotice();
|
||||
this._toggleSearchVisibility(false);
|
||||
},
|
||||
@ -192,9 +193,6 @@ VariablesView.prototype = {
|
||||
let prevList = this._list;
|
||||
let currList = this._list = this.document.createElement("scrollbox");
|
||||
|
||||
this._store.length = 0;
|
||||
this._itemsByElement.clear();
|
||||
|
||||
this.window.setTimeout(() => {
|
||||
prevList.removeEventListener("keypress", this._onViewKeyPress, false);
|
||||
prevList.removeEventListener("keydown", this._onViewKeyDown, false);
|
||||
@ -1173,7 +1171,7 @@ VariablesView.getterOrSetterDeleteCallback = function(aItem) {
|
||||
|
||||
// Make sure the right getter/setter to value override macro is applied
|
||||
// to the target object.
|
||||
aItem.ownerView.eval(aItem.evaluationMacro(aItem, ""));
|
||||
aItem.ownerView.eval(aItem, "");
|
||||
|
||||
return true; // Don't hide the element.
|
||||
};
|
||||
@ -2291,12 +2289,42 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this variable's path to the topmost scope.
|
||||
* Gets this variable's path to the topmost scope in the form of a string
|
||||
* meant for use via eval() or a similar approach.
|
||||
* For example, a symbolic name may look like "arguments['0']['foo']['bar']".
|
||||
* @return string
|
||||
*/
|
||||
get symbolicName() this._symbolicName,
|
||||
|
||||
/**
|
||||
* Gets this variable's symbolic path to the topmost scope.
|
||||
* @return array
|
||||
* @see Variable._buildSymbolicPath
|
||||
*/
|
||||
get symbolicPath() {
|
||||
if (this._symbolicPath) {
|
||||
return this._symbolicPath;
|
||||
}
|
||||
this._symbolicPath = this._buildSymbolicPath();
|
||||
return this._symbolicPath;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build this variable's path to the topmost scope in form of an array of
|
||||
* strings, one for each segment of the path.
|
||||
* For example, a symbolic path may look like ["0", "foo", "bar"].
|
||||
* @return array
|
||||
*/
|
||||
_buildSymbolicPath: function(path = []) {
|
||||
if (this.name) {
|
||||
path.unshift(this.name);
|
||||
if (this.ownerView instanceof Variable) {
|
||||
return this.ownerView._buildSymbolicPath(path);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns this variable's value from the descriptor if available.
|
||||
* @return any
|
||||
@ -2715,7 +2743,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
|
||||
if (!this._variablesView.preventDisableOnChange) {
|
||||
this._disable();
|
||||
}
|
||||
this.ownerView.eval(this.evaluationMacro(this, aString));
|
||||
this.ownerView.eval(this, aString);
|
||||
}
|
||||
}, e);
|
||||
},
|
||||
@ -2806,6 +2834,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
|
||||
},
|
||||
|
||||
_symbolicName: "",
|
||||
_symbolicPath: null,
|
||||
_absoluteName: "",
|
||||
_initialDescriptor: null,
|
||||
_separatorLabel: null,
|
||||
@ -2858,7 +2887,7 @@ Property.prototype["@@iterator"] = function*() {
|
||||
|
||||
/**
|
||||
* Forget everything recorded about added scopes, variables or properties.
|
||||
* @see VariablesView.createHierarchy
|
||||
* @see VariablesView.commitHierarchy
|
||||
*/
|
||||
VariablesView.prototype.clearHierarchy = function() {
|
||||
this._prevHierarchy.clear();
|
||||
@ -2866,17 +2895,13 @@ VariablesView.prototype.clearHierarchy = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Start recording a hierarchy of any added scopes, variables or properties.
|
||||
* @see VariablesView.commitHierarchy
|
||||
*/
|
||||
VariablesView.prototype.createHierarchy = function() {
|
||||
this._prevHierarchy = this._currHierarchy;
|
||||
this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.
|
||||
};
|
||||
|
||||
/**
|
||||
* Briefly flash the variables that changed between the previous and current
|
||||
* scope/variable/property hierarchies and reopen previously expanded nodes.
|
||||
* Perform operations on all the VariablesView Scopes, Variables and Properties
|
||||
* after you've added all the items you wanted.
|
||||
*
|
||||
* Calling this method is optional, and does the following:
|
||||
* - styles the items overridden by other items in parent scopes
|
||||
* - reopens the items which were previously expanded
|
||||
* - flashes the items whose values changed
|
||||
*/
|
||||
VariablesView.prototype.commitHierarchy = function() {
|
||||
for (let [, currItem] of this._currHierarchy) {
|
||||
|
@ -767,7 +767,11 @@ this.WidgetMethods = {
|
||||
*/
|
||||
set emptyText(aValue) {
|
||||
this._emptyText = aValue;
|
||||
this._widget.setAttribute("emptyText", aValue);
|
||||
|
||||
// Apply the emptyText attribute right now if there are no child items.
|
||||
if (!this._itemsByElement.size) {
|
||||
this._widget.setAttribute("emptyText", aValue);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,12 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* FastListWidget */
|
||||
|
||||
.fast-list-widget-container {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-container {
|
||||
|
@ -3469,7 +3469,6 @@ JSTerm.prototype = {
|
||||
_updateVariablesView: function JST__updateVariablesView(aOptions)
|
||||
{
|
||||
let view = aOptions.view;
|
||||
view.createHierarchy();
|
||||
view.empty();
|
||||
|
||||
// We need to avoid pruning the object inspection starting point.
|
||||
@ -3516,20 +3515,24 @@ JSTerm.prototype = {
|
||||
* @private
|
||||
* @param object aOptions
|
||||
* The options used for |this._updateVariablesView()|.
|
||||
* @param string aString
|
||||
* The string that the variables view wants to evaluate.
|
||||
* @param object aVar
|
||||
* The Variable object instance for the edited property.
|
||||
* @param string aValue
|
||||
* The value the edited property was changed to.
|
||||
*/
|
||||
_variablesViewEvaluate: function JST__variablesViewEvaluate(aOptions, aString)
|
||||
_variablesViewEvaluate:
|
||||
function JST__variablesViewEvaluate(aOptions, aVar, aValue)
|
||||
{
|
||||
let updater = this._updateVariablesView.bind(this, aOptions);
|
||||
let onEval = this._silentEvalCallback.bind(this, updater);
|
||||
let string = aVar.evaluationMacro(aVar, aValue);
|
||||
|
||||
let evalOptions = {
|
||||
frame: this.SELECTED_FRAME,
|
||||
bindObjectActor: aOptions.objectActor.actor,
|
||||
};
|
||||
|
||||
this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
|
||||
this.requestEvaluation(string, evalOptions).then(onEval, onEval);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4444,6 +4447,7 @@ var Utils = {
|
||||
case "Invalid HSTS Headers":
|
||||
case "Insecure Password Field":
|
||||
case "SSL":
|
||||
case "CORS":
|
||||
return CATEGORY_SECURITY;
|
||||
|
||||
default:
|
||||
|
@ -333,9 +333,14 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY customizeMenu.addToPanel.label "Add to Menu">
|
||||
<!ENTITY customizeMenu.addToPanel.accesskey "M">
|
||||
<!ENTITY customizeMenu.moveToToolbar.label "Move to Toolbar">
|
||||
<!ENTITY customizeMenu.moveToToolbar.accesskey "M">
|
||||
<!ENTITY customizeMenu.moveToToolbar.accesskey "o">
|
||||
<!-- LOCALIZATION NOTE (customizeMenu.moveToPanel.accesskey) can appear on the
|
||||
same context menu as menubarCmd and personalbarCmd, so they should have
|
||||
different access keys. customizeMenu.moveToToolbar and
|
||||
customizeMenu.moveToPanel are mutually exclusive, so can share access
|
||||
keys. -->
|
||||
<!ENTITY customizeMenu.moveToPanel.label "Move to Menu">
|
||||
<!ENTITY customizeMenu.moveToPanel.accesskey "M">
|
||||
<!ENTITY customizeMenu.moveToPanel.accesskey "o">
|
||||
<!ENTITY customizeMenu.removeFromToolbar.label "Remove from Toolbar">
|
||||
<!ENTITY customizeMenu.removeFromToolbar.accesskey "R">
|
||||
<!ENTITY customizeMenu.removeFromMenu.label "Remove from Menu">
|
||||
|
@ -50,10 +50,6 @@
|
||||
- button that toggles all breakpoints for all sources. -->
|
||||
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.tracingNotStarted.label): This is the text
|
||||
- displayed when tracing hasn't started in the debugger UI. -->
|
||||
<!ENTITY debuggerUI.tracingNotStarted.label "Tracing has not started.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
|
||||
- the button to start execution tracing. -->
|
||||
<!ENTITY debuggerUI.startTracing "Start Tracing">
|
||||
|
@ -85,6 +85,14 @@ noEventListenersText=No event listeners to display
|
||||
# when there are no stack frames.
|
||||
noStackFramesText=No stack frames to display
|
||||
|
||||
# LOCALIZATION NOTE (noStackFramesText): The text to display in the traces tab
|
||||
# when there are no function calls.
|
||||
noFunctionCallsText=No function calls to display
|
||||
|
||||
# LOCALIZATION NOTE (tracingNotStartedText): The text to display in the traces tab
|
||||
# when when tracing hasn't started yet.
|
||||
tracingNotStartedText=Tracing has not started
|
||||
|
||||
# LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
|
||||
# the user hovers over the checkbox used to toggle an event breakpoint.
|
||||
eventCheckboxTooltip=Toggle breaking on this event
|
||||
|
@ -30,7 +30,8 @@
|
||||
</content>
|
||||
|
||||
<handlers>
|
||||
<handler event="click" clickcount="1" action="this._onClick()"/>
|
||||
<handler event="click" button="0" clickcount="1" action="this._onClick()"/>
|
||||
<handler event="click" button="1" clickcount="1" action="event.stopPropagation(); this._onClose()"/>
|
||||
<handler event="dblclick" action="this._onDoubleClick(); event.stopPropagation();"/>
|
||||
</handlers>
|
||||
|
||||
|
@ -32,15 +32,6 @@ function testState(aState) {
|
||||
}
|
||||
}
|
||||
|
||||
function notifyPrecise()
|
||||
{
|
||||
Services.obs.notifyObservers(null, "metro_precise_input", null);
|
||||
}
|
||||
|
||||
function notifyImprecise()
|
||||
{
|
||||
Services.obs.notifyObservers(null, "metro_imprecise_input", null);
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "precise/imprecise input switcher",
|
||||
|
114
browser/metro/base/tests/mochitest/browser_tabs_container.js
Normal file
114
browser/metro/base/tests/mochitest/browser_tabs_container.js
Normal file
@ -0,0 +1,114 @@
|
||||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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";
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
yield waitForCondition(function () {
|
||||
return Elements.tabList && Elements.tabList.strip;
|
||||
});
|
||||
|
||||
if (!ContextUI.tabbarVisible) {
|
||||
ContextUI.displayTabs();
|
||||
}
|
||||
}
|
||||
function tearDown() {
|
||||
cleanUpOpenedTabs();
|
||||
}
|
||||
|
||||
function isElementVisible(elm) {
|
||||
return elm.ownerDocument.defaultView.getComputedStyle(elm).getPropertyValue("visibility") == "visible";
|
||||
}
|
||||
|
||||
|
||||
gTests.push({
|
||||
desc: "No scrollbuttons when tab strip doesnt require overflow",
|
||||
setUp: setup,
|
||||
run: function () {
|
||||
let tabStrip = Elements.tabList.strip;
|
||||
let tabs = Elements.tabList.strip.querySelectorAll("documenttab");
|
||||
|
||||
// sanity check tab count:
|
||||
is(tabs.length, 1, "1 tab present");
|
||||
|
||||
// test imprecise mode
|
||||
let imprecisePromise = waitForObserver("metro_imprecise_input");
|
||||
notifyImprecise();
|
||||
yield imprecisePromise;
|
||||
|
||||
ok(!isElementVisible(tabStrip._scrollButtonUp), "left scrollbutton is hidden in imprecise mode");
|
||||
ok(!isElementVisible(tabStrip._scrollButtonDown), "right scrollbutton is hidden in imprecise mode");
|
||||
|
||||
// test precise mode
|
||||
let precisePromise = waitForObserver("metro_precise_input");
|
||||
notifyPrecise();
|
||||
yield precisePromise;
|
||||
|
||||
todo(!isElementVisible(tabStrip._scrollButtonUp), "Bug 952297 - left scrollbutton is hidden in precise mode");
|
||||
ok(!isElementVisible(tabStrip._scrollButtonDown), "right scrollbutton is hidden in precise mode");
|
||||
},
|
||||
tearDown: tearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "Scrollbuttons not visible when tabstrip has overflow in imprecise input mode",
|
||||
setUp: function(){
|
||||
yield setup();
|
||||
// ensure we're in imprecise mode
|
||||
let imprecisePromise = waitForObserver("metro_imprecise_input");
|
||||
notifyImprecise();
|
||||
yield imprecisePromise;
|
||||
},
|
||||
run: function () {
|
||||
// add enough tabs to get overflow in the tabstrip
|
||||
let strip = Elements.tabList.strip;
|
||||
ok(strip && strip.scrollClientSize && strip.scrollSize, "Sanity check tabstrip strip is present and expected properties available");
|
||||
|
||||
while (strip.scrollSize <= strip.scrollClientSize) {
|
||||
yield addTab("about:mozilla");
|
||||
}
|
||||
|
||||
ok(!isElementVisible(Elements.tabList.strip._scrollButtonUp), "left scrollbutton is hidden in imprecise mode");
|
||||
ok(!isElementVisible(Elements.tabList.strip._scrollButtonDown), "right scrollbutton is hidden in imprecise mode");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "Scrollbuttons become visible when tabstrip has overflow in precise input mode",
|
||||
setUp: function(){
|
||||
yield setup();
|
||||
// ensure we're in precise mode
|
||||
let precisePromise = waitForObserver("metro_precise_input");
|
||||
notifyPrecise();
|
||||
yield precisePromise;
|
||||
},
|
||||
run: function () {
|
||||
let strip = Elements.tabList.strip;
|
||||
ok(strip && strip.scrollClientSize && strip.scrollSize, "Sanity check tabstrip is present and expected properties available");
|
||||
|
||||
// add enough tabs to get overflow in the tabstrip
|
||||
while (strip.scrollSize <= strip.scrollClientSize) {
|
||||
yield addTab("about:mozilla");
|
||||
}
|
||||
|
||||
let tabs = Elements.tabList.strip.querySelectorAll("documenttab");
|
||||
// select the first tab
|
||||
Elements.tabs.selectedTab = tabs[0];
|
||||
ok(isElementVisible(Elements.tabList.strip._scrollButtonDown), "right scrollbutton should be visible when tabList has overflow");
|
||||
todo(!isElementVisible(Elements.tabList.strip._scrollButtonUp), "Bug 952297 - left scrollbutton should not visible when 1st tab is selected and tablist has overflow");
|
||||
|
||||
|
||||
// select the last tab
|
||||
Elements.tabs.selectedTab = tabs[tabs.length-1];
|
||||
ok(isElementVisible(Elements.tabList.strip._scrollButtonUp), "left scrollbutton should be visible when tablist has overflow and last tab is selected");
|
||||
todo(!isElementVisible(Elements.tabList.strip._scrollButtonDown), "Bug 952297 - right scrollbutton should not visible when last tab is selected and tablist has overflow");
|
||||
|
||||
}
|
||||
});
|
@ -556,6 +556,25 @@ function waitForObserver(aObsEvent, aTimeoutMs) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*=============================================================================
|
||||
* Input mode helpers - these helpers notify observers to metro_precise_input
|
||||
* and metro_imprecise_input respectively, triggering the same behaviour as user touch or mouse input
|
||||
*
|
||||
* Usage: let promise = waitForObservers("metro_imprecise_input");
|
||||
* notifyImprecise();
|
||||
* yield promise; // you are now in imprecise mode
|
||||
*===========================================================================*/
|
||||
function notifyPrecise()
|
||||
{
|
||||
Services.obs.notifyObservers(null, "metro_precise_input", null);
|
||||
}
|
||||
|
||||
function notifyImprecise()
|
||||
{
|
||||
Services.obs.notifyObservers(null, "metro_imprecise_input", null);
|
||||
}
|
||||
|
||||
/*=============================================================================
|
||||
* Native input helpers - these helpers send input directly to the os
|
||||
* generating os level input events that get processed by widget and
|
||||
|
@ -51,6 +51,7 @@ support-files =
|
||||
[browser_remotetabs.js]
|
||||
[browser_snappedState.js]
|
||||
[browser_tabs.js]
|
||||
[browser_tabs_container.js]
|
||||
[browser_test.js]
|
||||
[browser_tiles.js]
|
||||
[browser_topsites.js]
|
||||
|
@ -22,7 +22,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
|
||||
const UITOUR_PERMISSION = "uitour";
|
||||
const PREF_PERM_BRANCH = "browser.uitour.";
|
||||
const MAX_BUTTONS = 4;
|
||||
|
||||
|
||||
this.UITour = {
|
||||
@ -126,34 +125,7 @@ this.UITour = {
|
||||
Cu.reportError("UITour: Target could not be resolved: " + data.target);
|
||||
return;
|
||||
}
|
||||
|
||||
let iconURL = null;
|
||||
if (typeof data.icon == "string")
|
||||
iconURL = this.resolveURL(contentDocument, data.icon);
|
||||
|
||||
let buttons = [];
|
||||
if (Array.isArray(data.buttons) && data.buttons.length > 0) {
|
||||
for (let buttonData of data.buttons) {
|
||||
if (typeof buttonData == "object" &&
|
||||
typeof buttonData.label == "string" &&
|
||||
typeof buttonData.callbackID == "string") {
|
||||
let button = {
|
||||
label: buttonData.label,
|
||||
callbackID: buttonData.callbackID,
|
||||
};
|
||||
|
||||
if (typeof buttonData.icon == "string")
|
||||
button.iconURL = this.resolveURL(contentDocument, buttonData.icon);
|
||||
|
||||
buttons.push(button);
|
||||
|
||||
if (buttons.length == MAX_BUTTONS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.showInfo(contentDocument, target, data.title, data.text, iconURL, buttons);
|
||||
this.showInfo(target, data.title, data.text);
|
||||
}).then(null, Cu.reportError);
|
||||
break;
|
||||
}
|
||||
@ -331,7 +303,11 @@ this.UITour = {
|
||||
if (uri.schemeIs("chrome"))
|
||||
return true;
|
||||
|
||||
if (!this.isSafeScheme(uri))
|
||||
let allowedSchemes = new Set(["https"]);
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
|
||||
allowedSchemes.add("http");
|
||||
|
||||
if (!allowedSchemes.has(uri.scheme))
|
||||
return false;
|
||||
|
||||
this.importPermissions();
|
||||
@ -339,50 +315,6 @@ this.UITour = {
|
||||
return permission == Services.perms.ALLOW_ACTION;
|
||||
},
|
||||
|
||||
isSafeScheme: function(aURI) {
|
||||
let allowedSchemes = new Set(["https"]);
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
|
||||
allowedSchemes.add("http");
|
||||
|
||||
if (!allowedSchemes.has(aURI.scheme))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
resolveURL: function(aDocument, aURL) {
|
||||
try {
|
||||
let uri = Services.io.newURI(aURL, null, aDocument.documentURIObject);
|
||||
|
||||
if (!this.isSafeScheme(uri))
|
||||
return null;
|
||||
|
||||
return uri.spec;
|
||||
} catch (e) {}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
sendPageCallback: function(aDocument, aCallbackID, aData = {}) {
|
||||
let detail = Cu.createObjectIn(aDocument.defaultView);
|
||||
detail.data = Cu.createObjectIn(detail);
|
||||
|
||||
for (let key of Object.keys(aData))
|
||||
detail.data[key] = aData[key];
|
||||
|
||||
Cu.makeObjectPropsNormal(detail.data);
|
||||
Cu.makeObjectPropsNormal(detail);
|
||||
|
||||
detail.callbackID = aCallbackID;
|
||||
|
||||
let event = new aDocument.defaultView.CustomEvent("mozUITourResponse", {
|
||||
bubbles: true,
|
||||
detail: detail
|
||||
});
|
||||
|
||||
aDocument.dispatchEvent(event);
|
||||
},
|
||||
|
||||
getTarget: function(aWindow, aTargetName, aSticky = false) {
|
||||
let deferred = Promise.defer();
|
||||
if (typeof aTargetName != "string" || !aTargetName) {
|
||||
@ -574,7 +506,7 @@ this.UITour = {
|
||||
this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
|
||||
},
|
||||
|
||||
showInfo: function(aContentDocument, aAnchor, aTitle = "", aDescription = "", aIconURL = "", aButtons = []) {
|
||||
showInfo: function(aAnchor, aTitle, aDescription) {
|
||||
function showInfoPanel(aAnchorEl) {
|
||||
aAnchorEl.focus();
|
||||
|
||||
@ -582,37 +514,13 @@ this.UITour = {
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
let tooltipTitle = document.getElementById("UITourTooltipTitle");
|
||||
let tooltipDesc = document.getElementById("UITourTooltipDescription");
|
||||
let tooltipIcon = document.getElementById("UITourTooltipIcon");
|
||||
let tooltipButtons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
if (tooltip.state == "open") {
|
||||
tooltip.hidePopup();
|
||||
}
|
||||
|
||||
tooltipTitle.textContent = aTitle || "";
|
||||
tooltipDesc.textContent = aDescription || "";
|
||||
tooltipIcon.src = aIconURL || "";
|
||||
tooltipIcon.hidden = !aIconURL;
|
||||
|
||||
while (tooltipButtons.firstChild)
|
||||
tooltipButtons.firstChild.remove();
|
||||
|
||||
for (let button of aButtons) {
|
||||
let el = document.createElement("button");
|
||||
el.setAttribute("label", button.label);
|
||||
if (button.iconURL)
|
||||
el.setAttribute("image", button.iconURL);
|
||||
|
||||
let callbackID = button.callbackID;
|
||||
el.addEventListener("command", event => {
|
||||
tooltip.hidePopup();
|
||||
this.sendPageCallback(aContentDocument, callbackID);
|
||||
});
|
||||
|
||||
tooltipButtons.appendChild(el);
|
||||
}
|
||||
|
||||
tooltipButtons.hidden = !aButtons.length;
|
||||
tooltipTitle.textContent = aTitle;
|
||||
tooltipDesc.textContent = aDescription;
|
||||
|
||||
tooltip.hidden = false;
|
||||
let alignment = "bottomcenter topright";
|
||||
@ -625,15 +533,9 @@ this.UITour = {
|
||||
},
|
||||
|
||||
hideInfo: function(aWindow) {
|
||||
let document = aWindow.document;
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
let tooltip = aWindow.document.getElementById("UITourTooltip");
|
||||
tooltip.hidePopup();
|
||||
this._setAppMenuStateForAnnotation(aWindow, "info", false);
|
||||
|
||||
let tooltipButtons = document.getElementById("UITourTooltipButtons");
|
||||
while (tooltipButtons.firstChild)
|
||||
tooltipButtons.firstChild.remove();
|
||||
},
|
||||
|
||||
showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
|
||||
|
@ -5,6 +5,6 @@ support-files =
|
||||
[browser_NetworkPrioritizer.js]
|
||||
[browser_SignInToWebsite.js]
|
||||
[browser_UITour.js]
|
||||
support-files = uitour.* image.png
|
||||
support-files = uitour.*
|
||||
[browser_taskbar_preview.js]
|
||||
run-if = os == "win"
|
||||
|
@ -4,7 +4,6 @@
|
||||
"use strict";
|
||||
|
||||
let gTestTab;
|
||||
let gContentWindow;
|
||||
let gContentAPI;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
@ -67,10 +66,10 @@ function loadTestPage(callback, host = "https://example.com/") {
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
let contentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = contentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
waitForFocus(callback, contentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@ -83,7 +82,6 @@ function test() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
@ -287,16 +285,11 @@ let tests = [
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "test title", "Popup should have correct title");
|
||||
is(desc.textContent, "test text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
popup.addEventListener("popuphidden", function onPopupHidden() {
|
||||
popup.removeEventListener("popuphidden", onPopupHidden);
|
||||
@ -318,16 +311,11 @@ let tests = [
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "urlbar title", "Popup should have correct title");
|
||||
is(desc.textContent, "urlbar text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
gContentAPI.showInfo("search", "search title", "search text");
|
||||
executeSoon(function() {
|
||||
@ -389,114 +377,6 @@ let tests = [
|
||||
}, "Info should be shown after showInfo() for fixed menu panel items");
|
||||
});
|
||||
},
|
||||
function test_info_icon(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
|
||||
is(title.textContent, "a title", "Popup should have correct title");
|
||||
is(desc.textContent, "some text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
gContentAPI.showInfo("urlbar", "a title", "some text", "image.png");
|
||||
},
|
||||
function test_info_buttons_1(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 2, "Popup should have two buttons");
|
||||
|
||||
is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
|
||||
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
|
||||
popup.addEventListener("popuphidden", function onPopupHidden() {
|
||||
popup.removeEventListener("popuphidden", onPopupHidden);
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
executeSoon(function() {
|
||||
is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
|
||||
});
|
||||
|
||||
let buttons = gContentWindow.makeButtons();
|
||||
gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
},
|
||||
function test_info_buttons_2(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 2, "Popup should have two buttons");
|
||||
|
||||
is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
|
||||
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
|
||||
popup.addEventListener("popuphidden", function onPopupHidden() {
|
||||
popup.removeEventListener("popuphidden", onPopupHidden);
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
executeSoon(function() {
|
||||
is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
|
||||
});
|
||||
|
||||
let buttons = gContentWindow.makeButtons();
|
||||
gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
},
|
||||
function test_pinnedTab(done) {
|
||||
is(UITour.pinnedTabs.get(window), null, "Should not already have a pinned tab");
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
@ -5,22 +5,6 @@
|
||||
<title>UITour test</title>
|
||||
<script type="application/javascript" src="uitour.js">
|
||||
</script>
|
||||
<script type="application/javascript">
|
||||
var callbackResult;
|
||||
function makeCallback(name) {
|
||||
return (function() {
|
||||
callbackResult = name;
|
||||
});
|
||||
}
|
||||
|
||||
// Defined in content to avoid weird issues when crossing between chrome/content.
|
||||
function makeButtons() {
|
||||
return [
|
||||
{label: "Button 1", callback: makeCallback("button1")},
|
||||
{label: "Button 2", callback: makeCallback("button2"), icon: "image.png"}
|
||||
];
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>UITour tests</h1>
|
||||
|
@ -25,6 +25,7 @@ if (typeof Mozilla == 'undefined') {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _sendEvent(action, data) {
|
||||
var event = new CustomEvent('mozUITour', {
|
||||
bubbles: true,
|
||||
@ -33,31 +34,10 @@ if (typeof Mozilla == 'undefined') {
|
||||
data: data || {}
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Sending mozUITour event: ", event);
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function _generateCallbackID() {
|
||||
return Math.random().toString(36).replace(/[^a-z]+/g, '');
|
||||
}
|
||||
|
||||
function _waitForCallback(callback) {
|
||||
var id = _generateCallbackID();
|
||||
|
||||
function listener(event) {
|
||||
if (typeof event.detail != "object")
|
||||
return;
|
||||
if (event.detail.callbackID != id)
|
||||
return;
|
||||
|
||||
document.removeEventListener("mozUITourResponse", listener);
|
||||
callback(event.detail.data);
|
||||
}
|
||||
document.addEventListener("mozUITourResponse", listener);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
|
||||
|
||||
Mozilla.UITour.showHighlight = function(target, effect) {
|
||||
@ -71,24 +51,11 @@ if (typeof Mozilla == 'undefined') {
|
||||
_sendEvent('hideHighlight');
|
||||
};
|
||||
|
||||
Mozilla.UITour.showInfo = function(target, title, text, icon, buttons) {
|
||||
var buttonData = [];
|
||||
if (Array.isArray(buttons)) {
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttonData.push({
|
||||
label: buttons[i].label,
|
||||
icon: buttons[i].icon,
|
||||
callbackID: _waitForCallback(buttons[i].callback)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Mozilla.UITour.showInfo = function(target, title, text) {
|
||||
_sendEvent('showInfo', {
|
||||
target: target,
|
||||
title: title,
|
||||
text: text,
|
||||
icon: icon,
|
||||
buttons: buttonData
|
||||
text: text
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1233,15 +1233,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
transition: height 100ms ease-out, width 100ms ease-out;
|
||||
}
|
||||
|
||||
#social-share-button {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button.png");
|
||||
}
|
||||
|
||||
#social-share-button[open],
|
||||
#social-share-button:hover:active {
|
||||
list-style-image: url("chrome://browser/skin/social/share-button-active.png");
|
||||
}
|
||||
|
||||
.social-share-toolbar {
|
||||
border-right: 1px solid #dedede;
|
||||
background: linear-gradient(to bottom, rgba(247,247,247,.99), rgba(247,247,247,.95));
|
||||
|
@ -101,37 +101,17 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
/* Make this button as narrow as the text inside it. */
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
#tracer-traces > scrollbox {
|
||||
overflow: scroll;
|
||||
/* Hack to enable hardware acceleration */
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
@ -167,10 +147,6 @@
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
@ -283,6 +283,24 @@
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* FastListWidget */
|
||||
|
||||
.fast-list-widget-container {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
}
|
||||
|
||||
.theme-dark .fast-list-widget-empty-text {
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.theme-light .fast-list-widget-empty-text {
|
||||
padding: 4px 8px;
|
||||
color: GrayText;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-container {
|
||||
|
@ -103,37 +103,17 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
/* Make this button as narrow as the text inside it. */
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
#tracer-traces > scrollbox {
|
||||
overflow: scroll;
|
||||
/* Hack to enable hardware acceleration */
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
@ -169,10 +149,6 @@
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
@ -283,6 +283,24 @@
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* FastListWidget */
|
||||
|
||||
.fast-list-widget-container {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
}
|
||||
|
||||
.theme-dark .fast-list-widget-empty-text {
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.theme-light .fast-list-widget-empty-text {
|
||||
padding: 4px 8px;
|
||||
color: GrayText;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-container {
|
||||
|
@ -22,10 +22,8 @@
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
#UITourTooltipIcon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 8px;
|
||||
#UITourTooltip {
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
#UITourTooltipTitle {
|
||||
@ -37,13 +35,3 @@
|
||||
#UITourTooltipDescription {
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
#UITourTooltipButtons {
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
#UITourTooltipButtons > button[image] > .button-box > .button-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 1.3 KiB |
@ -101,37 +101,17 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
/* Make this button as narrow as the text inside it. */
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
#tracer-traces > scrollbox {
|
||||
overflow: scroll;
|
||||
/* Hack to enable hardware acceleration */
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
@ -167,10 +147,6 @@
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
@ -287,6 +287,24 @@
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* FastListWidget */
|
||||
|
||||
.fast-list-widget-container {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
}
|
||||
|
||||
.theme-dark .fast-list-widget-empty-text {
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.theme-light .fast-list-widget-empty-text {
|
||||
padding: 4px 8px;
|
||||
color: GrayText;
|
||||
}
|
||||
|
||||
/* SideMenuWidget */
|
||||
|
||||
.side-menu-widget-container {
|
||||
|
@ -73,7 +73,7 @@ public class CodeGenerator {
|
||||
" JNIEnv *env = GetJNIForThread();\n\n" +
|
||||
" if (!env) {\n" +
|
||||
" ALOG_BRIDGE(\"Aborted: No env - %s\", __PRETTY_FUNCTION__);\n" +
|
||||
" return NULL;\n" +
|
||||
" return nullptr;\n" +
|
||||
" }\n\n" +
|
||||
" ").append(mCClassName).append("* ret = new ").append(mCClassName).append("(obj, env);\n" +
|
||||
" env->DeleteLocalRef(obj);\n" +
|
||||
@ -407,7 +407,7 @@ public class CodeGenerator {
|
||||
// Tack on the arguments, if any..
|
||||
.append(argumentContent)
|
||||
.append("), env);\n" +
|
||||
" env->PopLocalFrame(NULL);\n" +
|
||||
" env->PopLocalFrame(nullptr);\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
@ -484,7 +484,7 @@ public class CodeGenerator {
|
||||
" ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
|
||||
" env->ExceptionDescribe();\n" +
|
||||
" env->ExceptionClear();\n" +
|
||||
" env->PopLocalFrame(NULL);\n" +
|
||||
" env->PopLocalFrame(nullptr);\n" +
|
||||
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
|
||||
" }\n\n");
|
||||
|
||||
@ -498,11 +498,11 @@ public class CodeGenerator {
|
||||
} else if (!returnType.getCanonicalName().equals("void")) {
|
||||
// If we're a primitive-returning function, just return the directly-obtained primative
|
||||
// from the call to Java.
|
||||
wrapperMethodBodies.append(" env->PopLocalFrame(NULL);\n" +
|
||||
wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n" +
|
||||
" return temp;\n");
|
||||
} else {
|
||||
// If we don't return anything, just pop the stack frame and move on with life.
|
||||
wrapperMethodBodies.append(" env->PopLocalFrame(NULL);\n");
|
||||
wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n");
|
||||
}
|
||||
wrapperMethodBodies.append("}\n");
|
||||
}
|
||||
|
@ -880,6 +880,10 @@ EXPAND_MOZLIBNAME = $(foreach lib,$(1),$(DIST)/lib/$(LIB_PREFIX)$(lib).$(LIB_SUF
|
||||
PLY_INCLUDE = -I$(topsrcdir)/other-licenses/ply
|
||||
|
||||
export CL_INCLUDES_PREFIX
|
||||
# Make sure that the build system can handle non-ASCII characters
|
||||
# in environment variables to prevent it from breking silently on
|
||||
# non-English systems.
|
||||
export NONASCII
|
||||
|
||||
ifdef MOZ_GTK2_CFLAGS
|
||||
MOZ_GTK2_CFLAGS := -I$(topsrcdir)/widget/gtk/compat $(MOZ_GTK2_CFLAGS)
|
||||
|
@ -71,7 +71,7 @@ GCONF_VERSION=1.2.1
|
||||
GIO_VERSION=2.20
|
||||
STARTUP_NOTIFICATION_VERSION=0.8
|
||||
DBUS_VERSION=0.60
|
||||
SQLITE_VERSION=3.8.1
|
||||
SQLITE_VERSION=3.8.2
|
||||
|
||||
MSMANIFEST_TOOL=
|
||||
|
||||
@ -7705,6 +7705,12 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
dnl Make sure that the build system can handle non-ASCII characters
|
||||
dnl in environment variables to prevent it from breking silently on
|
||||
dnl non-English systems.
|
||||
NONASCII=$'\241\241'
|
||||
AC_SUBST(NONASCII)
|
||||
|
||||
dnl ========================================================
|
||||
dnl =
|
||||
dnl = Static Build Options
|
||||
|
@ -86,7 +86,6 @@ class nsIURI;
|
||||
class nsIWidget;
|
||||
class nsIWordBreaker;
|
||||
class nsIXPConnect;
|
||||
class nsIXPConnectJSObjectHolder;
|
||||
class nsNodeInfoManager;
|
||||
class nsPIDOMWindow;
|
||||
class nsPresContext;
|
||||
@ -1638,39 +1637,31 @@ public:
|
||||
*/
|
||||
static bool CanAccessNativeAnon();
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT
|
||||
static nsresult WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsISupports *native, const nsIID* aIID,
|
||||
JS::MutableHandle<JS::Value> vp,
|
||||
// If non-null aHolder will keep the Value alive
|
||||
// while there's a ref to it
|
||||
nsIXPConnectJSObjectHolder** aHolder = nullptr,
|
||||
bool aAllowWrapping = false)
|
||||
{
|
||||
return WrapNative(cx, scope, native, nullptr, aIID, vp, aHolder,
|
||||
aAllowWrapping);
|
||||
return WrapNative(cx, scope, native, nullptr, aIID, vp, aAllowWrapping);
|
||||
}
|
||||
|
||||
// Same as the WrapNative above, but use this one if aIID is nsISupports' IID.
|
||||
MOZ_WARN_UNUSED_RESULT
|
||||
static nsresult WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsISupports *native, JS::MutableHandle<JS::Value> vp,
|
||||
// If non-null aHolder will keep the Value alive
|
||||
// while there's a ref to it
|
||||
nsIXPConnectJSObjectHolder** aHolder = nullptr,
|
||||
bool aAllowWrapping = false)
|
||||
{
|
||||
return WrapNative(cx, scope, native, nullptr, nullptr, vp, aHolder,
|
||||
aAllowWrapping);
|
||||
return WrapNative(cx, scope, native, nullptr, nullptr, vp, aAllowWrapping);
|
||||
}
|
||||
|
||||
MOZ_WARN_UNUSED_RESULT
|
||||
static nsresult WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsISupports *native, nsWrapperCache *cache,
|
||||
JS::MutableHandle<JS::Value> vp,
|
||||
// If non-null aHolder will keep the Value alive
|
||||
// while there's a ref to it
|
||||
nsIXPConnectJSObjectHolder** aHolder = nullptr,
|
||||
bool aAllowWrapping = false)
|
||||
{
|
||||
return WrapNative(cx, scope, native, cache, nullptr, vp, aHolder,
|
||||
aAllowWrapping);
|
||||
return WrapNative(cx, scope, native, cache, nullptr, vp, aAllowWrapping);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2133,7 +2124,6 @@ private:
|
||||
static nsresult WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsISupports *native, nsWrapperCache *cache,
|
||||
const nsIID* aIID, JS::MutableHandle<JS::Value> vp,
|
||||
nsIXPConnectJSObjectHolder** aHolder,
|
||||
bool aAllowWrapping);
|
||||
|
||||
static nsresult DispatchEvent(nsIDocument* aDoc,
|
||||
|
@ -106,17 +106,21 @@ DOMParser::ParseFromString(const nsAString& str,
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 data(str);
|
||||
nsAutoCString utf8str;
|
||||
// Convert from UTF16 to UTF8 using fallible allocations
|
||||
if (!AppendUTF16toUTF8(str, utf8str, mozilla::fallible_t())) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// The new stream holds a reference to the buffer
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = NS_NewByteInputStream(getter_AddRefs(stream),
|
||||
data.get(), data.Length(),
|
||||
utf8str.get(), utf8str.Length(),
|
||||
NS_ASSIGNMENT_DEPEND);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
return ParseFromStream(stream, "UTF-8", data.Length(), contentType, aResult);
|
||||
return ParseFromStream(stream, "UTF-8", utf8str.Length(), contentType, aResult);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDocument>
|
||||
|
@ -55,7 +55,8 @@ namespace dom {
|
||||
#define DEFAULT_RECONNECTION_TIME_VALUE 5000
|
||||
#define MAX_RECONNECTION_TIME_VALUE PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
|
||||
|
||||
EventSource::EventSource() :
|
||||
EventSource::EventSource(nsPIDOMWindow* aOwnerWindow) :
|
||||
nsDOMEventTargetHelper(aOwnerWindow),
|
||||
mStatus(PARSE_STATE_OFF),
|
||||
mFrozen(false),
|
||||
mErrorLoadOnRedirect(false),
|
||||
@ -68,7 +69,6 @@ EventSource::EventSource() :
|
||||
mScriptLine(0),
|
||||
mInnerWindowID(0)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
EventSource::~EventSource()
|
||||
@ -186,10 +186,6 @@ EventSource::Init(nsISupports* aOwner,
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aOwner);
|
||||
NS_ENSURE_STATE(ownerWindow);
|
||||
MOZ_ASSERT(ownerWindow->IsInnerWindow());
|
||||
|
||||
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
|
||||
NS_ENSURE_STATE(sgo);
|
||||
nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
|
||||
@ -203,7 +199,6 @@ EventSource::Init(nsISupports* aOwner,
|
||||
|
||||
mPrincipal = principal;
|
||||
mWithCredentials = aWithCredentials;
|
||||
BindToOwner(ownerWindow);
|
||||
|
||||
// The conditional here is historical and not necessarily sane.
|
||||
if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
|
||||
@ -286,7 +281,15 @@ EventSource::Constructor(const GlobalObject& aGlobal,
|
||||
const EventSourceInit& aEventSourceInitDict,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<EventSource> eventSource = new EventSource();
|
||||
nsCOMPtr<nsPIDOMWindow> ownerWindow =
|
||||
do_QueryInterface(aGlobal.GetAsSupports());
|
||||
if (!ownerWindow) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(ownerWindow->IsInnerWindow());
|
||||
|
||||
nsRefPtr<EventSource> eventSource = new EventSource(ownerWindow);
|
||||
aRv = eventSource->Init(aGlobal.GetAsSupports(), aURL,
|
||||
aEventSourceInitDict.mWithCredentials);
|
||||
return eventSource.forget();
|
||||
|
@ -25,13 +25,6 @@
|
||||
#include "nsDeque.h"
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
|
||||
#define NS_EVENTSOURCE_CID \
|
||||
{ /* 755e2d2d-a836-4539-83f4-16b51156341f */ \
|
||||
0x755e2d2d, 0xa836, 0x4539, \
|
||||
{0x83, 0xf4, 0x16, 0xb5, 0x11, 0x56, 0x34, 0x1f} }
|
||||
|
||||
#define NS_EVENTSOURCE_CONTRACTID "@mozilla.org/eventsource;1"
|
||||
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
@ -53,7 +46,7 @@ class EventSource : public nsDOMEventTargetHelper
|
||||
friend class AsyncVerifyRedirectCallbackFwr;
|
||||
|
||||
public:
|
||||
EventSource();
|
||||
EventSource(nsPIDOMWindow* aOwnerWindow);
|
||||
virtual ~EventSource();
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(EventSource,
|
||||
|
@ -442,8 +442,9 @@ WebSocket::GetInterface(const nsIID& aIID, void** aResult)
|
||||
// WebSocket
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WebSocket::WebSocket()
|
||||
: mKeepingAlive(false),
|
||||
WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
|
||||
: nsDOMEventTargetHelper(aOwnerWindow),
|
||||
mKeepingAlive(false),
|
||||
mCheckMustKeepAlive(true),
|
||||
mOnCloseScheduled(false),
|
||||
mFailed(false),
|
||||
@ -457,8 +458,8 @@ WebSocket::WebSocket()
|
||||
mInnerWindowID(0)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
||||
|
||||
SetIsDOMBinding();
|
||||
MOZ_ASSERT(aOwnerWindow);
|
||||
MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
|
||||
}
|
||||
|
||||
WebSocket::~WebSocket()
|
||||
@ -560,8 +561,8 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
protocolArray.AppendElement(protocolElement);
|
||||
}
|
||||
|
||||
nsRefPtr<WebSocket> webSocket = new WebSocket();
|
||||
nsresult rv = webSocket->Init(aGlobal.GetContext(), principal, ownerWindow,
|
||||
nsRefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
|
||||
nsresult rv = webSocket->Init(aGlobal.GetContext(), principal,
|
||||
aUrl, protocolArray);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
@ -640,21 +641,17 @@ WebSocket::DisconnectFromOwner()
|
||||
nsresult
|
||||
WebSocket::Init(JSContext* aCx,
|
||||
nsIPrincipal* aPrincipal,
|
||||
nsPIDOMWindow* aOwnerWindow,
|
||||
const nsAString& aURL,
|
||||
nsTArray<nsString>& aProtocolArray)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(aOwnerWindow);
|
||||
MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
|
||||
|
||||
if (!PrefEnabled()) {
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
mPrincipal = aPrincipal;
|
||||
BindToOwner(aOwnerWindow);
|
||||
|
||||
// Attempt to kill "ghost" websocket: but usually too early for check to fail
|
||||
nsresult rv = CheckInnerWindowCorrectness();
|
||||
|
@ -140,13 +140,12 @@ public: // WebIDL interface:
|
||||
ErrorResult& aRv);
|
||||
|
||||
private: // constructor && distructor
|
||||
WebSocket();
|
||||
WebSocket(nsPIDOMWindow* aOwnerWindow);
|
||||
virtual ~WebSocket();
|
||||
|
||||
protected:
|
||||
nsresult Init(JSContext* aCx,
|
||||
nsIPrincipal* aPrincipal,
|
||||
nsPIDOMWindow* aOwnerWindow,
|
||||
const nsAString& aURL,
|
||||
nsTArray<nsString>& aProtocolArray);
|
||||
|
||||
|
@ -200,13 +200,13 @@ LOCAL_INCLUDES += [
|
||||
'/content/xbl/src',
|
||||
'/content/xml/content/src',
|
||||
'/content/xml/document/src',
|
||||
'/content/xslt/src/xpath',
|
||||
'/content/xul/content/src',
|
||||
'/content/xul/document/src',
|
||||
'/docshell/base',
|
||||
'/dom/base',
|
||||
'/dom/ipc',
|
||||
'/dom/workers',
|
||||
'/dom/xslt/xpath',
|
||||
'/image/src',
|
||||
'/js/ipc',
|
||||
'/js/xpconnect/src',
|
||||
|
@ -5613,12 +5613,9 @@ nsresult
|
||||
nsContentUtils::WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsISupports *native, nsWrapperCache *cache,
|
||||
const nsIID* aIID, JS::MutableHandle<JS::Value> vp,
|
||||
nsIXPConnectJSObjectHolder **aHolder,
|
||||
bool aAllowWrapping)
|
||||
{
|
||||
if (!native) {
|
||||
NS_ASSERTION(!aHolder || !*aHolder, "*aHolder should be null!");
|
||||
|
||||
vp.setNull();
|
||||
|
||||
return NS_OK;
|
||||
@ -5638,7 +5635,7 @@ nsContentUtils::WrapNative(JSContext *cx, JS::Handle<JSObject*> scope,
|
||||
nsresult rv = NS_OK;
|
||||
AutoPushJSContext context(cx);
|
||||
rv = sXPConnect->WrapNativeToJSVal(context, scope, native, cache, aIID,
|
||||
aAllowWrapping, vp.address(), aHolder);
|
||||
aAllowWrapping, vp.address());
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -5681,7 +5678,7 @@ nsContentUtils::CreateBlobBuffer(JSContext* aCx,
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
|
||||
return nsContentUtils::WrapNative(aCx, scope, blob, aBlob, nullptr, true);
|
||||
return nsContentUtils::WrapNative(aCx, scope, blob, aBlob, true);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
@ -26,6 +26,12 @@
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsILoadGroup.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace mozilla;
|
||||
@ -35,6 +41,75 @@ using namespace mozilla;
|
||||
static bool gDisableCORS = false;
|
||||
static bool gDisableCORSPrivateData = false;
|
||||
|
||||
static nsresult
|
||||
LogBlockedRequest(nsIRequest* aRequest)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// Get the innerWindowID associated with the XMLHTTPRequest
|
||||
PRUint64 innerWindowID = 0;
|
||||
|
||||
nsCOMPtr<nsILoadGroup> loadGroup;
|
||||
aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
|
||||
if (loadGroup) {
|
||||
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
||||
loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
|
||||
if (callbacks) {
|
||||
nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
|
||||
if(loadContext) {
|
||||
nsCOMPtr<nsIDOMWindow> window;
|
||||
loadContext->GetAssociatedWindow(getter_AddRefs(window));
|
||||
if (window) {
|
||||
nsCOMPtr<nsIDOMWindowUtils> du = do_GetInterface(window);
|
||||
du->GetCurrentInnerWindowID(&innerWindowID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!innerWindowID) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
||||
nsCOMPtr<nsIURI> aUri;
|
||||
channel->GetURI(getter_AddRefs(aUri));
|
||||
nsAutoCString spec;
|
||||
if (aUri) {
|
||||
aUri->GetSpec(spec);
|
||||
}
|
||||
|
||||
// Generate the error message
|
||||
nsXPIDLString blockedMessage;
|
||||
NS_ConvertUTF8toUTF16 specUTF16(spec);
|
||||
const PRUnichar* params[] = { specUTF16.get() };
|
||||
rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"CrossSiteRequestBlocked",
|
||||
params,
|
||||
blockedMessage);
|
||||
|
||||
// Build the error object and log it to the console
|
||||
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIScriptError> scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString msg(blockedMessage.get());
|
||||
rv = scriptError->InitWithWindowID(msg,
|
||||
NS_ConvertUTF8toUTF16(spec),
|
||||
EmptyString(),
|
||||
0,
|
||||
0,
|
||||
nsIScriptError::warningFlag,
|
||||
"CORS",
|
||||
innerWindowID);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = console->LogMessage(scriptError);
|
||||
return rv;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Preflight cache
|
||||
|
||||
@ -398,8 +473,11 @@ NS_IMETHODIMP
|
||||
nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext)
|
||||
{
|
||||
mRequestApproved = NS_SUCCEEDED(CheckRequestApproved(aRequest));
|
||||
nsresult rv = CheckRequestApproved(aRequest);
|
||||
mRequestApproved = NS_SUCCEEDED(rv);
|
||||
if (!mRequestApproved) {
|
||||
rv = LogBlockedRequest(aRequest);
|
||||
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
|
||||
if (sPreflightCache) {
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
||||
if (channel) {
|
||||
@ -622,6 +700,9 @@ nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
|
||||
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
|
||||
rv = CheckRequestApproved(aOldChannel);
|
||||
if (NS_FAILED(rv)) {
|
||||
rv = LogBlockedRequest(aOldChannel);
|
||||
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request");
|
||||
|
||||
if (sPreflightCache) {
|
||||
nsCOMPtr<nsIURI> oldURI;
|
||||
NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
|
||||
|
@ -80,11 +80,13 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDataChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIDOMDataChannel)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
|
||||
|
||||
nsDOMDataChannel::nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel)
|
||||
: mDataChannel(aDataChannel)
|
||||
nsDOMDataChannel::nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel,
|
||||
nsPIDOMWindow* aWindow)
|
||||
: nsDOMEventTargetHelper(aWindow && aWindow->IsOuterWindow() ?
|
||||
aWindow->GetCurrentInnerWindow() : aWindow)
|
||||
, mDataChannel(aDataChannel)
|
||||
, mBinaryType(DC_BINARY_TYPE_BLOB)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -107,13 +109,6 @@ nsDOMDataChannel::Init(nsPIDOMWindow* aDOMWindow)
|
||||
nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
|
||||
NS_ENSURE_STATE(principal);
|
||||
|
||||
if (aDOMWindow) {
|
||||
BindToOwner(aDOMWindow->IsOuterWindow() ?
|
||||
aDOMWindow->GetCurrentInnerWindow() : aDOMWindow);
|
||||
} else {
|
||||
BindToOwner(aDOMWindow);
|
||||
}
|
||||
|
||||
// Attempt to kill "ghost" DataChannel (if one can happen): but usually too early for check to fail
|
||||
rv = CheckInnerWindowCorrectness();
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
@ -499,7 +494,8 @@ NS_NewDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel,
|
||||
nsPIDOMWindow* aWindow,
|
||||
nsIDOMDataChannel** aDomDataChannel)
|
||||
{
|
||||
nsRefPtr<nsDOMDataChannel> domdc = new nsDOMDataChannel(aDataChannel);
|
||||
nsRefPtr<nsDOMDataChannel> domdc =
|
||||
new nsDOMDataChannel(aDataChannel, aWindow);
|
||||
|
||||
nsresult rv = domdc->Init(aWindow);
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
|
@ -25,7 +25,8 @@ class nsDOMDataChannel : public nsDOMEventTargetHelper,
|
||||
public mozilla::DataChannelListener
|
||||
{
|
||||
public:
|
||||
nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel);
|
||||
nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel,
|
||||
nsPIDOMWindow* aWindow);
|
||||
~nsDOMDataChannel();
|
||||
|
||||
nsresult Init(nsPIDOMWindow* aDOMWindow);
|
||||
|
@ -6805,7 +6805,7 @@ nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv)
|
||||
|
||||
JS::Rooted<JS::Value> v(cx);
|
||||
rv = nsContentUtils::WrapNative(cx, global, this, this, &v,
|
||||
nullptr, /* aAllowWrapping = */ false);
|
||||
/* aAllowWrapping = */ false);
|
||||
if (rv.Failed())
|
||||
return nullptr;
|
||||
newScope = &v.toObject();
|
||||
@ -11469,11 +11469,9 @@ nsIDocument::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aScope)
|
||||
JSAutoCompartment ac(aCx, obj);
|
||||
|
||||
JS::Rooted<JS::Value> winVal(aCx);
|
||||
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
|
||||
nsresult rv = nsContentUtils::WrapNative(aCx, obj, win,
|
||||
&NS_GET_IID(nsIDOMWindow),
|
||||
&winVal,
|
||||
getter_AddRefs(holder),
|
||||
false);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
|
@ -926,8 +926,8 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
|
||||
|
||||
JS::Rooted<JS::Value> targetv(ctx);
|
||||
JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
|
||||
nsContentUtils::WrapNative(ctx, global, aTarget, &targetv,
|
||||
nullptr, true);
|
||||
nsresult rv = nsContentUtils::WrapNative(ctx, global, aTarget, &targetv, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JS::Rooted<JSObject*> cpows(ctx);
|
||||
if (aCpows) {
|
||||
@ -1017,8 +1017,8 @@ nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
|
||||
defaultThisValue = aTarget;
|
||||
}
|
||||
JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
|
||||
nsContentUtils::WrapNative(ctx, global, defaultThisValue,
|
||||
&thisValue, nullptr, true);
|
||||
nsresult rv = nsContentUtils::WrapNative(ctx, global, defaultThisValue, &thisValue, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
// If the listener is a JS object which has receiveMessage function:
|
||||
if (!JS_GetProperty(ctx, object, "receiveMessage", &funval) ||
|
||||
|
@ -990,7 +990,7 @@ nsXMLHttpRequest::GetResponse(JSContext* aCx, ErrorResult& aRv)
|
||||
JS::Rooted<JS::Value> result(aCx, JSVAL_NULL);
|
||||
JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
|
||||
aRv = nsContentUtils::WrapNative(aCx, scope, mResponseBlob, &result,
|
||||
nullptr, true);
|
||||
true);
|
||||
return result;
|
||||
}
|
||||
case XML_HTTP_RESPONSE_TYPE_DOCUMENT:
|
||||
@ -1002,7 +1002,7 @@ nsXMLHttpRequest::GetResponse(JSContext* aCx, ErrorResult& aRv)
|
||||
JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
|
||||
JS::Rooted<JS::Value> result(aCx, JSVAL_NULL);
|
||||
aRv = nsContentUtils::WrapNative(aCx, scope, mResponseXML, &result,
|
||||
nullptr, true);
|
||||
true);
|
||||
return result;
|
||||
}
|
||||
case XML_HTTP_RESPONSE_TYPE_JSON:
|
||||
|
@ -100,6 +100,16 @@ protected:
|
||||
class nsXHREventTarget : public nsDOMEventTargetHelper,
|
||||
public nsIXMLHttpRequestEventTarget
|
||||
{
|
||||
protected:
|
||||
nsXHREventTarget(nsDOMEventTargetHelper* aOwner)
|
||||
: nsDOMEventTargetHelper(aOwner)
|
||||
{
|
||||
}
|
||||
|
||||
nsXHREventTarget()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
typedef mozilla::dom::XMLHttpRequestResponseType
|
||||
XMLHttpRequestResponseType;
|
||||
@ -129,10 +139,10 @@ class nsXMLHttpRequestUpload MOZ_FINAL : public nsXHREventTarget,
|
||||
{
|
||||
public:
|
||||
nsXMLHttpRequestUpload(nsDOMEventTargetHelper* aOwner)
|
||||
: nsXHREventTarget(aOwner)
|
||||
{
|
||||
BindToOwner(aOwner);
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_FORWARD_NSIXMLHTTPREQUESTEVENTTARGET(nsXHREventTarget::)
|
||||
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsXHREventTarget)
|
||||
|
@ -580,3 +580,4 @@ support-files =
|
||||
[test_xhr_send_readystate.html]
|
||||
[test_xhr_withCredentials.html]
|
||||
[test_file_from_blob.html]
|
||||
[test_warning_for_blocked_cross_site_request.html]
|
||||
|
@ -0,0 +1,92 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=713980
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 713980</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
||||
<!-- Load a cross-origin webfont without CORS (common pain point -->
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "bad_cross_origin_webfont";
|
||||
src: url('http://example.org/tests/content/base/test/csp/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
|
||||
}
|
||||
div#bad_webfont { font-family: "bad_cross_origin_webfont"; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var tests = {
|
||||
font : {
|
||||
uri_test : "font_bad",
|
||||
result : null
|
||||
},
|
||||
xhr : {
|
||||
uri_test : "http://invalid",
|
||||
result : null
|
||||
},
|
||||
}
|
||||
|
||||
function testsComplete() {
|
||||
for (var testName in tests) {
|
||||
var test = tests[testName];
|
||||
if (test.result == null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SpecialPowers.registerConsoleListener(function CORSMsgListener(aMsg) {
|
||||
if (!/Cross-Origin Request Blocked/.test(aMsg.message))
|
||||
return;
|
||||
|
||||
for (var testName in tests) {
|
||||
var test = tests[testName];
|
||||
if (test.result != null)
|
||||
continue;
|
||||
|
||||
var testRegexp = new RegExp(test.uri_test);
|
||||
if (testRegexp.test(aMsg.message)) {
|
||||
test.result = true;
|
||||
ok(true, "Got \"Cross-site request blocked\" warning message for " + testName);
|
||||
ok(aMsg.category == "CORS", "Got warning message with category \"" + aMsg.category + "\", expected \"CORS\"");
|
||||
// Got the message we wanted - make sure it is destined for a valid inner window
|
||||
ok(aMsg.windowID != 0, "Valid (non-zero) windowID for the cross-site request blocked message.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (testsComplete()) {
|
||||
SimpleTest.executeSoon(cleanup);
|
||||
}
|
||||
});
|
||||
|
||||
function cleanup() {
|
||||
SpecialPowers.postConsoleSentinel();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
// Send a cross-origin XHR request without CORS
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "http://example.org/tests/content/base/test/file_CrossSiteXHR_server.sjs?allowOrigin=http://invalid", true);
|
||||
xhr.send(null);
|
||||
|
||||
// Create a div that triggers a cross-origin webfont request
|
||||
// We do this in Javascript in order to guarantee the console listener has
|
||||
// already been registered; otherwise, there could be a race.
|
||||
var badDiv = document.createElement('div');
|
||||
badDiv.setAttribute('id', 'bad_webfont');
|
||||
document.body.appendChild(badDiv);
|
||||
</script>
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
1
content/canvas/test/webgl/skipped_tests_linux.txt
Normal file
1
content/canvas/test/webgl/skipped_tests_linux.txt
Normal file
@ -0,0 +1 @@
|
||||
conformance/more/conformance/quickCheckAPI-B2.html
|
@ -516,6 +516,7 @@ function start() {
|
||||
break;
|
||||
default:
|
||||
failingTestsFilename = 'failing_tests_linux.txt';
|
||||
skippedTestsFilename = 'skipped_tests_linux.txt';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -89,6 +89,7 @@ nsDOMEventTargetHelper::~nsDOMEventTargetHelper()
|
||||
void
|
||||
nsDOMEventTargetHelper::BindToOwner(nsPIDOMWindow* aOwner)
|
||||
{
|
||||
MOZ_ASSERT(!aOwner || aOwner->IsInnerWindow());
|
||||
nsCOMPtr<nsIGlobalObject> glob = do_QueryInterface(aOwner);
|
||||
BindToOwner(glob);
|
||||
}
|
||||
|
@ -43,6 +43,15 @@ public:
|
||||
// All objects coming through here are WebIDL objects
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
nsDOMEventTargetHelper(nsDOMEventTargetHelper* aOther)
|
||||
: mParentObject(nullptr)
|
||||
, mOwnerWindow(nullptr)
|
||||
, mHasOrHasHadOwnerWindow(false)
|
||||
{
|
||||
BindToOwner(aOther);
|
||||
// All objects coming through here are WebIDL objects
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
virtual ~nsDOMEventTargetHelper();
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
|
@ -39,6 +39,7 @@ class VideoFrameContainer;
|
||||
namespace dom {
|
||||
class TextTrack;
|
||||
class TimeRanges;
|
||||
class WakeLock;
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,7 +589,7 @@ protected:
|
||||
*/
|
||||
virtual void WakeLockCreate();
|
||||
virtual void WakeLockRelease();
|
||||
nsCOMPtr<nsIDOMMozWakeLock> mWakeLock;
|
||||
nsRefPtr<WakeLock> mWakeLock;
|
||||
|
||||
/**
|
||||
* Logs a warning message to the web console to report various failures.
|
||||
|
@ -14,6 +14,7 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class WakeLock;
|
||||
class VideoPlaybackQuality;
|
||||
|
||||
class HTMLVideoElement MOZ_FINAL : public HTMLMediaElement,
|
||||
@ -115,7 +116,7 @@ protected:
|
||||
virtual void WakeLockRelease();
|
||||
void WakeLockUpdate();
|
||||
|
||||
nsCOMPtr<nsIDOMMozWakeLock> mScreenWakeLock;
|
||||
nsRefPtr<WakeLock> mScreenWakeLock;
|
||||
|
||||
private:
|
||||
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
|
||||
|
22
content/html/content/reftests/pre-1-ref.html
Normal file
22
content/html/content/reftests/pre-1-ref.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<div style="width: 15em">
|
||||
<pre>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
</div>
|
||||
12
|
22
content/html/content/reftests/pre-1.html
Normal file
22
content/html/content/reftests/pre-1.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<div style="width: 15em">
|
||||
<pre>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre width=12>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre cols=12>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap width=12>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
<pre wrap cols=12>
|
||||
MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
|
||||
</pre>
|
||||
</div>
|
||||
<script>document.write(document.querySelectorAll('pre')[1].width);</script>
|
@ -27,6 +27,7 @@ skip-if(Android||B2G) == 649134-2.html 649134-2-ref.html
|
||||
|
||||
== href-attr-change-restyles.html href-attr-change-restyles-ref.html
|
||||
== figure.html figure-ref.html
|
||||
== pre-1.html pre-1-ref.html
|
||||
== table-border-1.html table-border-1-ref.html
|
||||
== table-border-2.html table-border-2-ref.html
|
||||
!= table-border-2.html table-border-2-notref.html
|
||||
|
@ -75,10 +75,10 @@
|
||||
|
||||
#include "nsCSSParser.h"
|
||||
#include "nsIMediaList.h"
|
||||
#include "nsIDOMWakeLock.h"
|
||||
#include "mozilla/dom/power/PowerManagerService.h"
|
||||
#include "mozilla/dom/WakeLock.h"
|
||||
|
||||
#include "ImageContainer.h"
|
||||
#include "nsIPowerManagerService.h"
|
||||
#include "nsRange.h"
|
||||
#include <algorithm>
|
||||
|
||||
@ -2245,13 +2245,14 @@ void
|
||||
HTMLMediaElement::WakeLockCreate()
|
||||
{
|
||||
if (!mWakeLock) {
|
||||
nsCOMPtr<nsIPowerManagerService> pmService =
|
||||
do_GetService(POWERMANAGERSERVICE_CONTRACTID);
|
||||
nsRefPtr<power::PowerManagerService> pmService =
|
||||
power::PowerManagerService::GetInstance();
|
||||
NS_ENSURE_TRUE_VOID(pmService);
|
||||
|
||||
pmService->NewWakeLock(NS_LITERAL_STRING("cpu"),
|
||||
OwnerDoc()->GetWindow(),
|
||||
getter_AddRefs(mWakeLock));
|
||||
ErrorResult rv;
|
||||
mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("cpu"),
|
||||
OwnerDoc()->GetInnerWindow(),
|
||||
rv);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2259,7 +2260,9 @@ void
|
||||
HTMLMediaElement::WakeLockRelease()
|
||||
{
|
||||
if (mWakeLock) {
|
||||
mWakeLock->Unlock();
|
||||
ErrorResult rv;
|
||||
mWakeLock->Unlock(rv);
|
||||
NS_WARN_IF_FALSE(!rv.Failed(), "Failed to unlock the wakelock.");
|
||||
mWakeLock = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -48,29 +48,12 @@ void
|
||||
HTMLPreElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
|
||||
nsRuleData* aData)
|
||||
{
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
|
||||
nsCSSValue* width = aData->ValueForWidth();
|
||||
if (width->GetUnit() == eCSSUnit_Null) {
|
||||
// width: int (html4 attribute)
|
||||
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
|
||||
if (value && value->Type() == nsAttrValue::eInteger)
|
||||
width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Char);
|
||||
}
|
||||
}
|
||||
if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
|
||||
nsCSSValue* whiteSpace = aData->ValueForWhiteSpace();
|
||||
if (whiteSpace->GetUnit() == eCSSUnit_Null) {
|
||||
// wrap: empty
|
||||
if (aAttributes->GetAttr(nsGkAtoms::wrap))
|
||||
whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_PRE_WRAP, eCSSUnit_Enumerated);
|
||||
|
||||
// width: int (html4 attribute)
|
||||
const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
|
||||
if (value && value->Type() == nsAttrValue::eInteger) {
|
||||
// Force wrap property on since we want to wrap at a width
|
||||
// boundary not just a newline.
|
||||
whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_PRE_WRAP, eCSSUnit_Enumerated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +65,6 @@ HTMLPreElement::IsAttributeMapped(const nsIAtom* aAttribute) const
|
||||
{
|
||||
static const MappedAttributeEntry attributes[] = {
|
||||
{ &nsGkAtoms::wrap },
|
||||
{ &nsGkAtoms::width },
|
||||
{ nullptr },
|
||||
};
|
||||
|
||||
|
@ -27,11 +27,11 @@
|
||||
|
||||
#include "nsEventDispatcher.h"
|
||||
#include "nsIDOMProgressEvent.h"
|
||||
#include "nsIPowerManagerService.h"
|
||||
#include "MediaError.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsIDOMWakeLock.h"
|
||||
#include "mozilla/dom/WakeLock.h"
|
||||
#include "mozilla/dom/power/PowerManagerService.h"
|
||||
#include "nsPerformance.h"
|
||||
#include "mozilla/dom/VideoPlaybackQuality.h"
|
||||
|
||||
@ -302,19 +302,22 @@ HTMLVideoElement::WakeLockUpdate()
|
||||
bool hidden = OwnerDoc()->Hidden();
|
||||
|
||||
if (mScreenWakeLock && (mPaused || hidden)) {
|
||||
mScreenWakeLock->Unlock();
|
||||
ErrorResult rv;
|
||||
mScreenWakeLock->Unlock(rv);
|
||||
NS_WARN_IF_FALSE(!rv.Failed(), "Failed to unlock the wakelock.");
|
||||
mScreenWakeLock = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mScreenWakeLock && !mPaused && !hidden) {
|
||||
nsCOMPtr<nsIPowerManagerService> pmService =
|
||||
do_GetService(POWERMANAGERSERVICE_CONTRACTID);
|
||||
nsRefPtr<power::PowerManagerService> pmService =
|
||||
power::PowerManagerService::GetInstance();
|
||||
NS_ENSURE_TRUE_VOID(pmService);
|
||||
|
||||
pmService->NewWakeLock(NS_LITERAL_STRING("screen"),
|
||||
OwnerDoc()->GetWindow(),
|
||||
getter_AddRefs(mScreenWakeLock));
|
||||
ErrorResult rv;
|
||||
mScreenWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("screen"),
|
||||
OwnerDoc()->GetInnerWindow(),
|
||||
rv);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,8 @@ ConvertSegmentToAudioBlock(AudioSegment* aSegment, AudioChunk* aBlock)
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
|
||||
AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// According to spec, number of outputs is always 1.
|
||||
mLastChunks.SetLength(1);
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
|
||||
~AudioNodeExternalInputStream();
|
||||
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) MOZ_OVERRIDE;
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
// For storing pointers and data about input tracks, like the last TrackTick which
|
||||
|
@ -269,8 +269,7 @@ AudioNodeStream::ObtainInputBlock(AudioChunk& aTmpChunk, uint32_t aPortIndex)
|
||||
MediaStream* s = mInputs[i]->GetSource();
|
||||
AudioNodeStream* a = static_cast<AudioNodeStream*>(s);
|
||||
MOZ_ASSERT(a == s->AsAudioNodeStream());
|
||||
if (a->IsFinishedOnGraphThread() ||
|
||||
a->IsAudioParamStream()) {
|
||||
if (a->IsAudioParamStream()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -399,29 +398,20 @@ AudioNodeStream::UpMixDownMixChunk(const AudioChunk* aChunk,
|
||||
// The MediaStreamGraph guarantees that this is actually one block, for
|
||||
// AudioNodeStreams.
|
||||
void
|
||||
AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
|
||||
AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
|
||||
{
|
||||
if (mMarkAsFinishedAfterThisBlock) {
|
||||
// This stream was finished the last time that we looked at it, and all
|
||||
// of the depending streams have finished their output as well, so now
|
||||
// it's time to mark this stream as finished.
|
||||
FinishOutput();
|
||||
}
|
||||
|
||||
EnsureTrack(AUDIO_TRACK, mSampleRate);
|
||||
// No more tracks will be coming
|
||||
mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
|
||||
|
||||
uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
|
||||
mLastChunks.SetLength(outputCount);
|
||||
|
||||
if (mMuted) {
|
||||
if (mMuted || IsFinishedOnGraphThread()) {
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
} else {
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
mLastChunks[i].SetNull(0);
|
||||
}
|
||||
|
||||
// We need to generate at least one input
|
||||
uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount());
|
||||
OutputChunks inputChunks;
|
||||
@ -430,11 +420,21 @@ AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
|
||||
ObtainInputBlock(inputChunks[i], i);
|
||||
}
|
||||
bool finished = false;
|
||||
#ifdef DEBUG
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
// Clear chunks so we can detect if ProduceAudioBlock fails to set them.
|
||||
mLastChunks[i].SetNull(0);
|
||||
}
|
||||
#endif
|
||||
if (maxInputs <= 1 && mEngine->OutputCount() <= 1) {
|
||||
mEngine->ProduceAudioBlock(this, inputChunks[0], &mLastChunks[0], &finished);
|
||||
} else {
|
||||
mEngine->ProduceAudioBlocksOnPorts(this, inputChunks, mLastChunks, &finished);
|
||||
}
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE,
|
||||
"Invalid WebAudio chunk size");
|
||||
}
|
||||
if (finished) {
|
||||
mMarkAsFinishedAfterThisBlock = true;
|
||||
}
|
||||
@ -446,7 +446,16 @@ AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
|
||||
}
|
||||
}
|
||||
|
||||
AdvanceOutputSegment();
|
||||
if (!IsFinishedOnGraphThread()) {
|
||||
// Don't output anything after we've finished!
|
||||
AdvanceOutputSegment();
|
||||
if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) {
|
||||
// This stream was finished the last time that we looked at it, and all
|
||||
// of the depending streams have finished their output as well, so now
|
||||
// it's time to mark this stream as finished.
|
||||
FinishOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -84,7 +84,7 @@ public:
|
||||
mAudioParamStream = true;
|
||||
}
|
||||
|
||||
virtual AudioNodeStream* AsAudioNodeStream() { return this; }
|
||||
virtual AudioNodeStream* AsAudioNodeStream() MOZ_OVERRIDE { return this; }
|
||||
|
||||
// Graph thread only
|
||||
void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
@ -92,7 +92,7 @@ public:
|
||||
void SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
|
||||
dom::ChannelCountMode aChannelCountMoe,
|
||||
dom::ChannelInterpretation aChannelInterpretation);
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
TrackTicks GetCurrentPosition();
|
||||
bool IsAudioParamStream() const
|
||||
{
|
||||
|
@ -200,29 +200,20 @@ MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
|
||||
mHaveBlockedForPlayState(false),
|
||||
mHaveBlockedForStateMachineNotPlaying(false)
|
||||
{
|
||||
mStream->AddMainThreadListener(this);
|
||||
mListener = new DecodedStreamGraphListener(mStream);
|
||||
mListener = new DecodedStreamGraphListener(mStream, this);
|
||||
mStream->AddListener(mListener);
|
||||
}
|
||||
|
||||
MediaDecoder::DecodedStreamData::~DecodedStreamData()
|
||||
{
|
||||
mStream->RemoveMainThreadListener(this);
|
||||
mListener->Forget();
|
||||
mStream->Destroy();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
|
||||
{
|
||||
mDecoder->NotifyDecodedStreamMainThreadStateChanged();
|
||||
if (mStream->IsFinished()) {
|
||||
mListener->SetFinishedOnMainThread(true);
|
||||
}
|
||||
}
|
||||
|
||||
MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream)
|
||||
: mMutex("MediaDecoder::DecodedStreamData::mMutex"),
|
||||
MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream,
|
||||
DecodedStreamData* aData)
|
||||
: mData(aData),
|
||||
mMutex("MediaDecoder::DecodedStreamData::mMutex"),
|
||||
mStream(aStream),
|
||||
mLastOutputTime(aStream->GetCurrentTime()),
|
||||
mStreamFinishedOnMainThread(false)
|
||||
@ -239,6 +230,29 @@ MediaDecoder::DecodedStreamGraphListener::NotifyOutput(MediaStreamGraph* aGraph,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::DecodedStreamGraphListener::DoNotifyFinished()
|
||||
{
|
||||
if (mData && mData->mDecoder) {
|
||||
if (mData->mDecoder->GetState() == PLAY_STATE_PLAYING) {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(mData->mDecoder, &MediaDecoder::PlaybackEnded);
|
||||
NS_DispatchToCurrentThread(event);
|
||||
}
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
mStreamFinishedOnMainThread = true;
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::DecodedStreamGraphListener::NotifyFinished(MediaStreamGraph* aGraph)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
|
||||
aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
|
||||
}
|
||||
|
||||
void MediaDecoder::DestroyDecodedStream()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
@ -326,19 +340,6 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::NotifyDecodedStreamMainThreadStateChanged()
|
||||
{
|
||||
if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream &&
|
||||
mDecodedStream->mStream->IsFinished()) {
|
||||
mTriggerPlaybackEndedWhenSourceStreamFinishes = false;
|
||||
if (GetState() == PLAY_STATE_PLAYING) {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &MediaDecoder::PlaybackEnded);
|
||||
NS_DispatchToCurrentThread(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
|
||||
bool aFinishWhenEnded)
|
||||
{
|
||||
@ -421,7 +422,6 @@ MediaDecoder::MediaDecoder() :
|
||||
mCalledResourceLoaded(false),
|
||||
mIgnoreProgressData(false),
|
||||
mInfiniteStream(false),
|
||||
mTriggerPlaybackEndedWhenSourceStreamFinishes(false),
|
||||
mOwner(nullptr),
|
||||
mFrameBufferLength(0),
|
||||
mPinnedForSeek(false),
|
||||
@ -936,12 +936,6 @@ void MediaDecoder::PlaybackEnded()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
if (mDecodedStream && !mDecodedStream->mStream->IsFinished()) {
|
||||
// Wait for it to finish before firing PlaybackEnded()
|
||||
mTriggerPlaybackEndedWhenSourceStreamFinishes = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
|
||||
OutputStreamData& os = mOutputStreams[i];
|
||||
if (os.mStream->IsDestroyed()) {
|
||||
@ -1289,7 +1283,12 @@ void MediaDecoder::PlaybackPositionChanged()
|
||||
// and we don't want to override the seek algorithm and change the
|
||||
// current time after the seek has started but before it has
|
||||
// completed.
|
||||
mCurrentTime = mDecoderStateMachine->GetCurrentTime();
|
||||
if (GetDecodedStream()) {
|
||||
mCurrentTime = mDecoderStateMachine->GetCurrentTimeViaMediaStreamSync()/
|
||||
static_cast<double>(USECS_PER_S);
|
||||
} else {
|
||||
mCurrentTime = mDecoderStateMachine->GetCurrentTime();
|
||||
}
|
||||
}
|
||||
mDecoderStateMachine->ClearPositionChangeFlag();
|
||||
}
|
||||
|
@ -350,13 +350,11 @@ public:
|
||||
// replaying after the input as ended. In the latter case, the new source is
|
||||
// not connected to streams created by captureStreamUntilEnded.
|
||||
|
||||
struct DecodedStreamData MOZ_FINAL : public MainThreadMediaStreamListener {
|
||||
struct DecodedStreamData {
|
||||
DecodedStreamData(MediaDecoder* aDecoder,
|
||||
int64_t aInitialTime, SourceMediaStream* aStream);
|
||||
~DecodedStreamData();
|
||||
|
||||
virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
|
||||
|
||||
StreamTime GetLastOutputTime() { return mListener->GetLastOutputTime(); }
|
||||
bool IsFinished() { return mListener->IsFinishedOnMainThread(); }
|
||||
|
||||
@ -400,8 +398,11 @@ public:
|
||||
|
||||
class DecodedStreamGraphListener : public MediaStreamListener {
|
||||
public:
|
||||
DecodedStreamGraphListener(MediaStream* aStream);
|
||||
DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData);
|
||||
virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE;
|
||||
virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
|
||||
|
||||
void DoNotifyFinished();
|
||||
|
||||
StreamTime GetLastOutputTime()
|
||||
{
|
||||
@ -410,13 +411,18 @@ public:
|
||||
}
|
||||
void Forget()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Main thread only");
|
||||
mData = nullptr;
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
mStream = nullptr;
|
||||
}
|
||||
void SetFinishedOnMainThread(bool aFinished)
|
||||
bool SetFinishedOnMainThread(bool aFinished)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
bool result = !mStreamFinishedOnMainThread;
|
||||
mStreamFinishedOnMainThread = aFinished;
|
||||
return result;
|
||||
}
|
||||
bool IsFinishedOnMainThread()
|
||||
{
|
||||
@ -424,6 +430,9 @@ public:
|
||||
return mStreamFinishedOnMainThread;
|
||||
}
|
||||
private:
|
||||
// Main thread only
|
||||
DecodedStreamData* mData;
|
||||
|
||||
Mutex mMutex;
|
||||
// Protected by mMutex
|
||||
nsRefPtr<MediaStream> mStream;
|
||||
@ -465,12 +474,6 @@ public:
|
||||
* Decoder monitor must be held.
|
||||
*/
|
||||
void UpdateStreamBlockingForStateMachinePlaying();
|
||||
/**
|
||||
* Called when the state of mDecodedStream as visible on the main thread
|
||||
* has changed. In particular we want to know when the stream has finished
|
||||
* so we can call PlaybackEnded.
|
||||
*/
|
||||
void NotifyDecodedStreamMainThreadStateChanged();
|
||||
nsTArray<OutputStreamData>& OutputStreams()
|
||||
{
|
||||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
@ -1134,10 +1137,6 @@ protected:
|
||||
// True if the stream is infinite (e.g. a webradio).
|
||||
bool mInfiniteStream;
|
||||
|
||||
// True if NotifyDecodedStreamMainThreadStateChanged should retrigger
|
||||
// PlaybackEnded when mDecodedStream->mStream finishes.
|
||||
bool mTriggerPlaybackEndedWhenSourceStreamFinishes;
|
||||
|
||||
// Start timer to update download progress information.
|
||||
nsresult StartProgress();
|
||||
|
||||
|
@ -681,34 +681,34 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
nsAutoTArray<VideoData*,10> video;
|
||||
// It's OK to hold references to the VideoData only the decoder thread
|
||||
// pops from the queue.
|
||||
mReader->VideoQueue().GetElementsAfter(stream->mNextVideoTime + mStartTime, &video);
|
||||
mReader->VideoQueue().GetElementsAfter(stream->mNextVideoTime, &video);
|
||||
VideoSegment output;
|
||||
for (uint32_t i = 0; i < video.Length(); ++i) {
|
||||
VideoData* v = video[i];
|
||||
if (stream->mNextVideoTime + mStartTime < v->mTime) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms",
|
||||
if (stream->mNextVideoTime < v->mTime) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lldus",
|
||||
mDecoder.get(), mediaStream,
|
||||
v->mTime - (stream->mNextVideoTime + mStartTime)));
|
||||
v->mTime - stream->mNextVideoTime));
|
||||
// Write last video frame to catch up. mLastVideoImage can be null here
|
||||
// which is fine, it just means there's no video.
|
||||
WriteVideoToMediaStream(stream->mLastVideoImage,
|
||||
v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
|
||||
v->mTime - stream->mNextVideoTime, stream->mLastVideoImageDisplaySize,
|
||||
&output);
|
||||
stream->mNextVideoTime = v->mTime - mStartTime;
|
||||
stream->mNextVideoTime = v->mTime;
|
||||
}
|
||||
if (stream->mNextVideoTime + mStartTime < v->GetEndTime()) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms",
|
||||
if (stream->mNextVideoTime < v->GetEndTime()) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lldus to MediaStream %p for %lldus",
|
||||
mDecoder.get(), v->mTime, mediaStream,
|
||||
v->GetEndTime() - (stream->mNextVideoTime + mStartTime)));
|
||||
v->GetEndTime() - stream->mNextVideoTime));
|
||||
WriteVideoToMediaStream(v->mImage,
|
||||
v->GetEndTime() - (stream->mNextVideoTime + mStartTime), v->mDisplay,
|
||||
v->GetEndTime() - stream->mNextVideoTime, v->mDisplay,
|
||||
&output);
|
||||
stream->mNextVideoTime = v->GetEndTime() - mStartTime;
|
||||
stream->mNextVideoTime = v->GetEndTime();
|
||||
stream->mLastVideoImage = v->mImage;
|
||||
stream->mLastVideoImageDisplaySize = v->mDisplay;
|
||||
} else {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
|
||||
mDecoder.get(), v->mTime));
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lldus (end %lldus) to MediaStream",
|
||||
mDecoder.get(), v->mTime, v->GetEndTime()));
|
||||
}
|
||||
}
|
||||
if (output.GetDuration() > 0) {
|
||||
@ -1313,7 +1313,7 @@ void MediaDecoderStateMachine::StopPlayback()
|
||||
mDecoder->NotifyPlaybackStopped();
|
||||
|
||||
if (IsPlaying()) {
|
||||
mPlayDuration += DurationToUsecs(TimeStamp::Now() - mPlayStartTime);
|
||||
mPlayDuration = GetClock();
|
||||
mPlayStartTime = TimeStamp();
|
||||
}
|
||||
// Notify the audio thread, so that it notices that we've stopped playing,
|
||||
@ -1336,6 +1336,15 @@ void MediaDecoderStateMachine::SetSyncPointForMediaStream()
|
||||
mSyncPointInDecodedStream = mStartTime + mPlayDuration;
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetCurrentTimeViaMediaStreamSync()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
NS_ASSERTION(mSyncPointInDecodedStream >= 0, "Should have set up sync point");
|
||||
DecodedStreamData* stream = mDecoder->GetDecodedStream();
|
||||
StreamTime streamDelta = stream->GetLastOutputTime() - mSyncPointInMediaStream;
|
||||
return mSyncPointInDecodedStream + MediaTimeToMicroseconds(streamDelta);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::StartPlayback()
|
||||
{
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get()));
|
||||
@ -2369,10 +2378,14 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||
}
|
||||
|
||||
StopAudioThread();
|
||||
if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING) {
|
||||
// When we're decoding to a stream, the stream's main-thread finish signal
|
||||
// will take care of calling MediaDecoder::PlaybackEnded.
|
||||
if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
|
||||
!mDecoder->GetDecodedStream()) {
|
||||
int64_t videoTime = HasVideo() ? mVideoFrameEndTime : 0;
|
||||
int64_t clockTime = std::max(mEndTime, std::max(videoTime, GetAudioClock()));
|
||||
UpdatePlaybackPosition(clockTime);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
@ -2409,7 +2422,6 @@ void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
|
||||
int64_t
|
||||
MediaDecoderStateMachine::GetAudioClock()
|
||||
{
|
||||
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
|
||||
// We must hold the decoder monitor while using the audio stream off the
|
||||
// audio thread to ensure that it doesn't get destroyed on the audio thread
|
||||
// while we're using it.
|
||||
@ -2444,8 +2456,8 @@ int64_t MediaDecoderStateMachine::GetVideoStreamPosition()
|
||||
return mBasePosition + pos * mPlaybackRate + mStartTime;
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetClock() {
|
||||
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
|
||||
int64_t MediaDecoderStateMachine::GetClock()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
// Determine the clock time. If we've got audio, and we've not reached
|
||||
@ -2457,9 +2469,7 @@ int64_t MediaDecoderStateMachine::GetClock() {
|
||||
if (!IsPlaying()) {
|
||||
clock_time = mPlayDuration + mStartTime;
|
||||
} else if (stream) {
|
||||
NS_ASSERTION(mSyncPointInDecodedStream >= 0, "Should have set up sync point");
|
||||
StreamTime streamDelta = stream->GetLastOutputTime() - mSyncPointInMediaStream;
|
||||
clock_time = mSyncPointInDecodedStream + MediaTimeToMicroseconds(streamDelta);
|
||||
clock_time = GetCurrentTimeViaMediaStreamSync();
|
||||
} else {
|
||||
int64_t audio_time = GetAudioClock();
|
||||
if (HasAudio() && !mAudioCompleted && audio_time != -1) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user