Merge m-c to s-c

This commit is contained in:
Nick Alexander 2014-01-07 15:22:58 -08:00
commit 00a971da6b
773 changed files with 14114 additions and 10499 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
{
"revision": "ac80bbe24c39a813ab65b98bb56dce2e42207b22",
"revision": "d5d2b5614c32e17c5c3c736181a66bfa3fc5c3ef",
"repo_path": "/integration/gaia-central"
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
: []
});
},

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,12 @@
overflow-y: auto;
}
/* FastListWidget */
.fast-list-widget-container {
overflow: auto;
}
/* SideMenuWidget */
.side-menu-widget-container {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
conformance/more/conformance/quickCheckAPI-B2.html

View File

@ -516,6 +516,7 @@ function start() {
break;
default:
failingTestsFilename = 'failing_tests_linux.txt';
skippedTestsFilename = 'skipped_tests_linux.txt';
break;
}
break;

View File

@ -89,6 +89,7 @@ nsDOMEventTargetHelper::~nsDOMEventTargetHelper()
void
nsDOMEventTargetHelper::BindToOwner(nsPIDOMWindow* aOwner)
{
MOZ_ASSERT(!aOwner || aOwner->IsInnerWindow());
nsCOMPtr<nsIGlobalObject> glob = do_QueryInterface(aOwner);
BindToOwner(glob);
}

View File

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

View File

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

View File

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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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