mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-12 21:05:36 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
38668bd0ef
@ -853,6 +853,7 @@ var gBrowserInit = {
|
||||
|
||||
let mm = window.getGroupMessageManager("browsers");
|
||||
mm.loadFrameScript("chrome://browser/content/content.js", true);
|
||||
mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
|
||||
|
||||
// initialize observers and listeners
|
||||
// and give C++ access to gBrowser
|
||||
|
86
browser/base/content/content-UITour.js
Normal file
86
browser/base/content/content-UITour.js
Normal file
@ -0,0 +1,86 @@
|
||||
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
|
||||
const UITOUR_PERMISSION = "uitour";
|
||||
|
||||
let UITourListener = {
|
||||
handleEvent: function (event) {
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.enabled")) {
|
||||
return;
|
||||
}
|
||||
if (!this.ensureTrustedOrigin()) {
|
||||
return;
|
||||
}
|
||||
addMessageListener("UITour:SendPageCallback", this);
|
||||
sendAsyncMessage("UITour:onPageEvent", {detail: event.detail, type: event.type});
|
||||
},
|
||||
|
||||
isTestingOrigin: function(aURI) {
|
||||
if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add any testing origins (comma-seperated) to the whitelist for the session.
|
||||
for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) {
|
||||
try {
|
||||
let testingURI = Services.io.newURI(origin, null, null);
|
||||
if (aURI.prePath == testingURI.prePath) {
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// This function is copied from UITour.jsm.
|
||||
isSafeScheme: function(aURI) {
|
||||
let allowedSchemes = new Set(["https", "about"]);
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
|
||||
allowedSchemes.add("http");
|
||||
|
||||
if (!allowedSchemes.has(aURI.scheme))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
ensureTrustedOrigin: function() {
|
||||
if (content.top != content)
|
||||
return false;
|
||||
|
||||
let uri = content.document.documentURIObject;
|
||||
|
||||
if (uri.schemeIs("chrome"))
|
||||
return true;
|
||||
|
||||
if (!this.isSafeScheme(uri))
|
||||
return false;
|
||||
|
||||
let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
|
||||
if (permission == Services.perms.ALLOW_ACTION)
|
||||
return true;
|
||||
|
||||
return this.isTestingOrigin(uri);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "UITour:SendPageCallback":
|
||||
this.sendPageCallback(aMessage.data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
sendPageCallback: function (detail) {
|
||||
let doc = content.document;
|
||||
let event = new doc.defaultView.CustomEvent("mozUITourResponse", {
|
||||
bubbles: true,
|
||||
detail: Cu.cloneInto(detail, doc.defaultView)
|
||||
});
|
||||
doc.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
addEventListener("mozUITour", UITourListener, false, true);
|
@ -25,8 +25,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
|
||||
"resource:///modules/PluginContent.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
"resource:///modules/UITour.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
|
||||
"resource:///modules/FormSubmitObserver.jsm");
|
||||
|
||||
@ -122,15 +120,6 @@ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
|
||||
.getService(Ci.nsIEventListenerService)
|
||||
.addSystemEventListener(global, "contextmenu", handleContentContextMenu, true);
|
||||
|
||||
} else {
|
||||
addEventListener("mozUITour", function(event) {
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.enabled"))
|
||||
return;
|
||||
|
||||
let handled = UITour.onPageEvent(event);
|
||||
if (handled)
|
||||
addEventListener("pagehide", UITour);
|
||||
}, false, true);
|
||||
}
|
||||
|
||||
let AboutHomeListener = {
|
||||
|
@ -73,11 +73,25 @@ Sanitizer.prototype = {
|
||||
if (openWindowsIndex != -1) {
|
||||
itemsToClear.splice(openWindowsIndex, 1);
|
||||
let item = this.items.openWindows;
|
||||
if (!item.clear()) {
|
||||
// When cancelled, reject the deferred and return the promise:
|
||||
deferred.reject();
|
||||
return deferred.promise;
|
||||
|
||||
function onWindowsCleaned() {
|
||||
try {
|
||||
let clearedPromise = this.sanitize(itemsToClear);
|
||||
clearedPromise.then(deferred.resolve, deferred.reject);
|
||||
} catch(e) {
|
||||
let error = "Sanitizer threw after closing windows: " + e;
|
||||
Cu.reportError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
let ok = item.clear(onWindowsCleaned.bind(this));
|
||||
// When cancelled, reject immediately
|
||||
if (!ok) {
|
||||
deferred.reject("Sanitizer canceled closing windows");
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Cache the range of times to clear
|
||||
@ -465,11 +479,15 @@ Sanitizer.prototype = {
|
||||
win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
|
||||
}
|
||||
},
|
||||
clear: function()
|
||||
clear: function(aCallback)
|
||||
{
|
||||
// NB: this closes all *browser* windows, not other windows like the library, about window,
|
||||
// browser console, etc.
|
||||
|
||||
if (!aCallback) {
|
||||
throw "Sanitizer's openWindows clear() requires a callback.";
|
||||
}
|
||||
|
||||
// Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
|
||||
// dialogs
|
||||
let existingWindow = Services.appShell.hiddenDOMWindow;
|
||||
@ -507,7 +525,39 @@ Sanitizer.prototype = {
|
||||
let newWindow = existingWindow.openDialog("chrome://browser/content/", "_blank",
|
||||
features, defaultArgs);
|
||||
|
||||
// Then close all those windows we checked:
|
||||
// Window creation and destruction is asynchronous. We need to wait
|
||||
// until all existing windows are fully closed, and the new window is
|
||||
// fully open, before continuing. Otherwise the rest of the sanitizer
|
||||
// could run too early (and miss new cookies being set when a page
|
||||
// closes) and/or run too late (and not have a fully-formed window yet
|
||||
// in existence). See bug 1088137.
|
||||
let newWindowOpened = false;
|
||||
function onWindowOpened(subject, topic, data) {
|
||||
if (subject != newWindow)
|
||||
return;
|
||||
|
||||
Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
|
||||
newWindowOpened = true;
|
||||
// If we're the last thing to happen, invoke callback.
|
||||
if (numWindowsClosing == 0)
|
||||
aCallback();
|
||||
}
|
||||
|
||||
let numWindowsClosing = windowList.length;
|
||||
function onWindowClosed() {
|
||||
numWindowsClosing--;
|
||||
if (numWindowsClosing == 0) {
|
||||
Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
|
||||
// If we're the last thing to happen, invoke callback.
|
||||
if (newWindowOpened)
|
||||
aCallback();
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
|
||||
Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
|
||||
|
||||
// Start the process of closing windows
|
||||
while (windowList.length) {
|
||||
windowList.pop().close();
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ browser.jar:
|
||||
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
|
||||
* content/browser/chatWindow.xul (content/chatWindow.xul)
|
||||
content/browser/content.js (content/content.js)
|
||||
content/browser/content-UITour.js (content/content-UITour.js)
|
||||
content/browser/defaultthemes/1.footer.jpg (content/defaultthemes/1.footer.jpg)
|
||||
content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg)
|
||||
content/browser/defaultthemes/1.icon.jpg (content/defaultthemes/1.icon.jpg)
|
||||
|
@ -17,6 +17,9 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
|
||||
"resource:///modules/AboutHome.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
"resource:///modules/UITour.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
@ -2553,3 +2556,12 @@ let E10SUINotification = {
|
||||
|
||||
var components = [BrowserGlue, ContentPermissionPrompt];
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
||||
|
||||
|
||||
// Listen for UITour messages.
|
||||
// Do it here instead of the UITour module itself so that the UITour module is lazy loaded
|
||||
// when the first message is received.
|
||||
let globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
globalMM.addMessageListener("UITour:onPageEvent", function(aMessage) {
|
||||
UITour.onPageEvent(aMessage, aMessage.data);
|
||||
});
|
||||
|
@ -76,6 +76,7 @@ support-files =
|
||||
doc_pretty-print-2.html
|
||||
doc_pretty-print-3.html
|
||||
doc_pretty-print-on-paused.html
|
||||
doc_promise.html
|
||||
doc_random-javascript.html
|
||||
doc_recursion-stack.html
|
||||
doc_same-line-functions.html
|
||||
@ -278,6 +279,7 @@ skip-if = os == "linux" || e10s # Bug 888811 & bug 891176
|
||||
[browser_dbg_variables-view-03.js]
|
||||
[browser_dbg_variables-view-04.js]
|
||||
[browser_dbg_variables-view-05.js]
|
||||
[browser_dbg_variables-view-06.js]
|
||||
[browser_dbg_variables-view-accessibility.js]
|
||||
[browser_dbg_variables-view-data.js]
|
||||
[browser_dbg_variables-view-edit-cancel.js]
|
||||
|
122
browser/devtools/debugger/test/browser_dbg_variables-view-06.js
Normal file
122
browser/devtools/debugger/test/browser_dbg_variables-view-06.js
Normal file
@ -0,0 +1,122 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that Promises get their internal state added as psuedo properties.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_promise.html";
|
||||
|
||||
const test = Task.async(function* () {
|
||||
const [tab, debuggee, panel] = yield initDebugger(TAB_URL);
|
||||
yield ensureSourceIs(panel, "doc_promise.html", true);
|
||||
|
||||
const scopes = waitForCaretAndScopes(panel, 21);
|
||||
executeSoon(debuggee.doPause);
|
||||
yield scopes;
|
||||
|
||||
const variables = panel.panelWin.DebuggerView.Variables;
|
||||
ok(variables, "Should get the variables view.");
|
||||
|
||||
const scope = [...variables][0];
|
||||
ok(scope, "Should get the current function's scope.");
|
||||
|
||||
const promiseVariables = [...scope].filter(([name]) =>
|
||||
["p", "f", "r"].indexOf(name) !== -1);
|
||||
|
||||
is(promiseVariables.length, 3,
|
||||
"Should have our 3 promise variables: p, f, r");
|
||||
|
||||
for (let [name, item] of promiseVariables) {
|
||||
info("Expanding variable '" + name + "'");
|
||||
let expanded = once(variables, "fetched");
|
||||
item.expand();
|
||||
yield expanded;
|
||||
|
||||
let foundState = false;
|
||||
switch (name) {
|
||||
case "p":
|
||||
for (let [property, { value }] of item) {
|
||||
if (property !== "<state>") {
|
||||
isnot(property, "<value>",
|
||||
"A pending promise shouldn't have a value");
|
||||
isnot(property, "<reason>",
|
||||
"A pending promise shouldn't have a reason");
|
||||
continue;
|
||||
}
|
||||
|
||||
foundState = true;
|
||||
is(value, "pending", "The state should be pending.");
|
||||
}
|
||||
ok(foundState, "We should have found the <state> property.");
|
||||
break;
|
||||
|
||||
case "f":
|
||||
let foundValue = false;
|
||||
for (let [property, value] of item) {
|
||||
if (property === "<state>") {
|
||||
foundState = true;
|
||||
is(value.value, "fulfilled", "The state should be fulfilled.");
|
||||
} else if (property === "<value>") {
|
||||
foundValue = true;
|
||||
|
||||
let expanded = once(variables, "fetched");
|
||||
value.expand();
|
||||
yield expanded;
|
||||
|
||||
let expectedProps = new Map([["a", 1], ["b", 2], ["c", 3]]);
|
||||
for (let [prop, val] of value) {
|
||||
if (prop === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
ok(expectedProps.has(prop), "The property should be expected.");
|
||||
is(val.value, expectedProps.get(prop), "The property value should be correct.");
|
||||
expectedProps.delete(prop);
|
||||
}
|
||||
is(Object.keys(expectedProps).length, 0,
|
||||
"Should have found all of the expected properties.");
|
||||
} else {
|
||||
isnot(property, "<reason>",
|
||||
"A fulfilled promise shouldn't have a reason");
|
||||
}
|
||||
}
|
||||
ok(foundState, "We should have found the <state> property.");
|
||||
ok(foundValue, "We should have found the <value> property.");
|
||||
break;
|
||||
|
||||
case "r":
|
||||
let foundReason = false;
|
||||
for (let [property, value] of item) {
|
||||
if (property === "<state>") {
|
||||
foundState = true;
|
||||
is(value.value, "rejected", "The state should be rejected.");
|
||||
} else if (property === "<reason>") {
|
||||
foundReason = true;
|
||||
|
||||
let expanded = once(variables, "fetched");
|
||||
value.expand();
|
||||
yield expanded;
|
||||
|
||||
let foundMessage = false;
|
||||
for (let [prop, val] of value) {
|
||||
if (prop !== "message") {
|
||||
continue;
|
||||
}
|
||||
foundMessage = true;
|
||||
is(val.value, "uh oh", "Should have the correct error message.");
|
||||
}
|
||||
ok(foundMessage, "Should have found the error's message");
|
||||
} else {
|
||||
isnot(property, "<value>",
|
||||
"A rejected promise shouldn't have a value");
|
||||
}
|
||||
}
|
||||
ok(foundState, "We should have found the <state> property.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debugger;
|
||||
|
||||
resumeDebuggerThenCloseAndFinish(panel);
|
||||
});
|
30
browser/devtools/debugger/test/doc_promise.html
Normal file
30
browser/devtools/debugger/test/doc_promise.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Debugger + Promise test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
window.pending = new Promise(function () {});
|
||||
window.fulfilled = Promise.resolve({ a: 1, b: 2, c: 3 });
|
||||
window.rejected = Promise.reject(new Error("uh oh"));
|
||||
|
||||
window.doPause = function () {
|
||||
var p = window.pending;
|
||||
var f = window.fulfilled;
|
||||
var r = window.rejected;
|
||||
debugger;
|
||||
};
|
||||
|
||||
// Attach an error handler so that the logs don't have a warning about an
|
||||
// unhandled, rejected promise.
|
||||
window.rejected.then(null, function () {});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,75 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { VariablesView } = Cu.import("resource:///modules/devtools/VariablesView.jsm", {});
|
||||
|
||||
const PENDING = {
|
||||
"type": "object",
|
||||
"class": "Promise",
|
||||
"actor": "conn0.pausedobj35",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"promiseState": {
|
||||
"state": "pending"
|
||||
},
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {},
|
||||
"ownPropertiesLength": 0,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
const FULFILLED = {
|
||||
"type": "object",
|
||||
"class": "Promise",
|
||||
"actor": "conn0.pausedobj35",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"promiseState": {
|
||||
"state": "fulfilled",
|
||||
"value": 10
|
||||
},
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {},
|
||||
"ownPropertiesLength": 0,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
const REJECTED = {
|
||||
"type": "object",
|
||||
"class": "Promise",
|
||||
"actor": "conn0.pausedobj35",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"promiseState": {
|
||||
"state": "rejected",
|
||||
"reason": 10
|
||||
},
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {},
|
||||
"ownPropertiesLength": 0,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
equal(VariablesView.getString(PENDING, { concise: true }), "Promise");
|
||||
equal(VariablesView.getString(PENDING), 'Promise {<state>: "pending"}');
|
||||
|
||||
equal(VariablesView.getString(FULFILLED, { concise: true }), "Promise");
|
||||
equal(VariablesView.getString(FULFILLED), 'Promise {<state>: "fulfilled", <value>: 10}');
|
||||
|
||||
equal(VariablesView.getString(REJECTED, { concise: true }), "Promise");
|
||||
equal(VariablesView.getString(REJECTED), 'Promise {<state>: "rejected", <reason>: 10}');
|
||||
}
|
@ -7,3 +7,4 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
[test_bezierCanvas.js]
|
||||
[test_cubicBezier.js]
|
||||
[test_undoStack.js]
|
||||
[test_VariablesView_getString_promise.js]
|
@ -3575,6 +3575,17 @@ VariablesView.stringifiers.byObjectKind = {
|
||||
|
||||
let {preview} = aGrip;
|
||||
let props = [];
|
||||
|
||||
if (aGrip.class == "Promise" && aGrip.promiseState) {
|
||||
let { state, value, reason } = aGrip.promiseState;
|
||||
props.push("<state>: " + VariablesView.getString(state));
|
||||
if (state == "fulfilled") {
|
||||
props.push("<value>: " + VariablesView.getString(value, { concise: true }));
|
||||
} else if (state == "rejected") {
|
||||
props.push("<reason>: " + VariablesView.getString(reason, { concise: true }));
|
||||
}
|
||||
}
|
||||
|
||||
for (let key of Object.keys(preview.ownProperties || {})) {
|
||||
let value = preview.ownProperties[key];
|
||||
let valueString = "";
|
||||
|
@ -170,6 +170,16 @@ VariablesViewController.prototype = {
|
||||
_populateFromObject: function(aTarget, aGrip) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (aGrip.class === "Promise" && aGrip.promiseState) {
|
||||
const { state, value, reason } = aGrip.promiseState;
|
||||
aTarget.addItem("<state>", { value: state });
|
||||
if (state === "fulfilled") {
|
||||
this.addExpander(aTarget.addItem("<value>", { value }), value);
|
||||
} else if (state === "rejected") {
|
||||
this.addExpander(aTarget.addItem("<reason>", { value: reason }), reason);
|
||||
}
|
||||
}
|
||||
|
||||
let objectClient = this._getObjectClient(aGrip);
|
||||
objectClient.getPrototypeAndProperties(aResponse => {
|
||||
let { ownProperties, prototype } = aResponse;
|
||||
|
@ -1172,17 +1172,15 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
|
||||
*/
|
||||
_renderObjectActor: function(objectActor, options = {})
|
||||
{
|
||||
let widget = null;
|
||||
let {preview} = objectActor;
|
||||
let widget = Widgets.ObjectRenderers.byClass[objectActor.class];
|
||||
|
||||
if (preview && preview.kind) {
|
||||
let { preview } = objectActor;
|
||||
if ((!widget || (widget.canRender && !widget.canRender(objectActor)))
|
||||
&& preview
|
||||
&& preview.kind) {
|
||||
widget = Widgets.ObjectRenderers.byKind[preview.kind];
|
||||
}
|
||||
|
||||
if (!widget || (widget.canRender && !widget.canRender(objectActor))) {
|
||||
widget = Widgets.ObjectRenderers.byClass[objectActor.class];
|
||||
}
|
||||
|
||||
if (!widget || (widget.canRender && !widget.canRender(objectActor))) {
|
||||
widget = Widgets.JSObject;
|
||||
}
|
||||
@ -2299,6 +2297,118 @@ Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
||||
this.element = this._anchor(str, { className: className });
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a concise representation of an object.
|
||||
*/
|
||||
_renderConciseObject: function()
|
||||
{
|
||||
this.element = this._anchor(this.objectActor.class,
|
||||
{ className: "cm-variable" });
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the `<class> { ` prefix of an object.
|
||||
*/
|
||||
_renderObjectPrefix: function()
|
||||
{
|
||||
let { kind } = this.objectActor.preview;
|
||||
this.element = this.el("span.kind-" + kind);
|
||||
this._anchor(this.objectActor.class, { className: "cm-variable" });
|
||||
this._text(" { ");
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the ` }` suffix of an object.
|
||||
*/
|
||||
_renderObjectSuffix: function()
|
||||
{
|
||||
this._text(" }");
|
||||
},
|
||||
|
||||
/**
|
||||
* Render an object property.
|
||||
*
|
||||
* @param String key
|
||||
* The property name.
|
||||
* @param Object value
|
||||
* The property value, as an RDP grip.
|
||||
* @param nsIDOMNode container
|
||||
* The container node to render to.
|
||||
* @param Boolean needsComma
|
||||
* True if there was another property before this one and we need to
|
||||
* separate them with a comma.
|
||||
* @param Boolean valueIsText
|
||||
* Add the value as is, don't treat it as a grip and pass it to
|
||||
* `_renderValueGrip`.
|
||||
*/
|
||||
_renderObjectProperty: function(key, value, container, needsComma, valueIsText = false)
|
||||
{
|
||||
if (needsComma) {
|
||||
this._text(", ");
|
||||
}
|
||||
|
||||
container.appendChild(this.el("span.cm-property", key));
|
||||
this._text(": ");
|
||||
|
||||
if (valueIsText) {
|
||||
this._text(value);
|
||||
} else {
|
||||
let valueElem = this.message._renderValueGrip(value, { concise: true });
|
||||
container.appendChild(valueElem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render this object's properties.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node to render to.
|
||||
* @param Boolean needsComma
|
||||
* True if there was another property before this one and we need to
|
||||
* separate them with a comma.
|
||||
*/
|
||||
_renderObjectProperties: function(container, needsComma)
|
||||
{
|
||||
let { preview } = this.objectActor;
|
||||
let { ownProperties, safeGetterValues } = preview;
|
||||
|
||||
let shown = 0;
|
||||
|
||||
let getValue = desc => {
|
||||
if (desc.get) {
|
||||
return "Getter";
|
||||
} else if (desc.set) {
|
||||
return "Setter";
|
||||
} else {
|
||||
return desc.value;
|
||||
}
|
||||
};
|
||||
|
||||
for (let key of Object.keys(ownProperties || {})) {
|
||||
this._renderObjectProperty(key, getValue(ownProperties[key]), container,
|
||||
shown > 0 || needsComma,
|
||||
ownProperties[key].get || ownProperties[key].set);
|
||||
shown++;
|
||||
}
|
||||
|
||||
let ownPropertiesShown = shown;
|
||||
|
||||
for (let key of Object.keys(safeGetterValues || {})) {
|
||||
this._renderObjectProperty(key, safeGetterValues[key].getterValue,
|
||||
container, shown > 0 || needsComma);
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (typeof preview.ownPropertiesLength == "number" &&
|
||||
ownPropertiesShown < preview.ownPropertiesLength) {
|
||||
this._text(", ");
|
||||
|
||||
let n = preview.ownPropertiesLength - ownPropertiesShown;
|
||||
let str = VariablesView.stringifiers._getNMoreString(n);
|
||||
this._anchor(str);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render an anchor with a given text content and link.
|
||||
*
|
||||
@ -3085,6 +3195,42 @@ Widgets.ObjectRenderers.add({
|
||||
},
|
||||
}); // Widgets.ObjectRenderers.byKind.DOMNode
|
||||
|
||||
/**
|
||||
* The widget user for displaying Promise objects.
|
||||
*/
|
||||
Widgets.ObjectRenderers.add({
|
||||
byClass: "Promise",
|
||||
|
||||
render: function()
|
||||
{
|
||||
let { ownProperties, safeGetterValues } = this.objectActor.preview;
|
||||
if ((!ownProperties && !safeGetterValues) || this.options.concise) {
|
||||
this._renderConciseObject();
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderObjectPrefix();
|
||||
let container = this.element;
|
||||
let addedPromiseInternalProps = false;
|
||||
|
||||
if (this.objectActor.promiseState) {
|
||||
const { state, value, reason } = this.objectActor.promiseState;
|
||||
|
||||
this._renderObjectProperty("<state>", state, container, false);
|
||||
addedPromiseInternalProps = true;
|
||||
|
||||
if (state == "fulfilled") {
|
||||
this._renderObjectProperty("<value>", value, container, true);
|
||||
} else if (state == "rejected") {
|
||||
this._renderObjectProperty("<reason>", reason, container, true);
|
||||
}
|
||||
}
|
||||
|
||||
this._renderObjectProperties(container, addedPromiseInternalProps);
|
||||
this._renderObjectSuffix();
|
||||
}
|
||||
}); // Widgets.ObjectRenderers.byClass.Promise
|
||||
|
||||
/**
|
||||
* The widget used for displaying generic JS object previews.
|
||||
*/
|
||||
@ -3093,73 +3239,15 @@ Widgets.ObjectRenderers.add({
|
||||
|
||||
render: function()
|
||||
{
|
||||
let {preview} = this.objectActor;
|
||||
let {ownProperties, safeGetterValues} = preview;
|
||||
|
||||
let { ownProperties, safeGetterValues } = this.objectActor.preview;
|
||||
if ((!ownProperties && !safeGetterValues) || this.options.concise) {
|
||||
this.element = this._anchor(this.objectActor.class,
|
||||
{ className: "cm-variable" });
|
||||
this._renderConciseObject();
|
||||
return;
|
||||
}
|
||||
|
||||
let container = this.element = this.el("span.kind-" + preview.kind);
|
||||
this._anchor(this.objectActor.class, { className: "cm-variable" });
|
||||
this._text(" { ");
|
||||
|
||||
let addProperty = (str) => {
|
||||
container.appendChild(this.el("span.cm-property", str));
|
||||
};
|
||||
|
||||
let shown = 0;
|
||||
for (let key of Object.keys(ownProperties || {})) {
|
||||
if (shown > 0) {
|
||||
this._text(", ");
|
||||
}
|
||||
|
||||
let value = ownProperties[key];
|
||||
|
||||
addProperty(key);
|
||||
this._text(": ");
|
||||
|
||||
if (value.get) {
|
||||
addProperty("Getter");
|
||||
} else if (value.set) {
|
||||
addProperty("Setter");
|
||||
} else {
|
||||
let valueElem = this.message._renderValueGrip(value.value, { concise: true });
|
||||
container.appendChild(valueElem);
|
||||
}
|
||||
|
||||
shown++;
|
||||
}
|
||||
|
||||
let ownPropertiesShown = shown;
|
||||
|
||||
for (let key of Object.keys(safeGetterValues || {})) {
|
||||
if (shown > 0) {
|
||||
this._text(", ");
|
||||
}
|
||||
|
||||
addProperty(key);
|
||||
this._text(": ");
|
||||
|
||||
let value = safeGetterValues[key].getterValue;
|
||||
let valueElem = this.message._renderValueGrip(value, { concise: true });
|
||||
container.appendChild(valueElem);
|
||||
|
||||
shown++;
|
||||
}
|
||||
|
||||
if (typeof preview.ownPropertiesLength == "number" &&
|
||||
ownPropertiesShown < preview.ownPropertiesLength) {
|
||||
this._text(", ");
|
||||
|
||||
let n = preview.ownPropertiesLength - ownPropertiesShown;
|
||||
let str = VariablesView.stringifiers._getNMoreString(n);
|
||||
this._anchor(str);
|
||||
}
|
||||
|
||||
this._text(" }");
|
||||
this._renderObjectPrefix();
|
||||
this._renderObjectProperties(this.element, false);
|
||||
this._renderObjectSuffix();
|
||||
},
|
||||
}); // Widgets.ObjectRenderers.byKind.Object
|
||||
|
||||
|
@ -84,6 +84,26 @@ let inputTests = [
|
||||
inspectable: true,
|
||||
variablesViewLabel: "String[5]"
|
||||
},
|
||||
|
||||
// 9
|
||||
{
|
||||
// XXX: Can't test fulfilled and rejected promises, because promises get
|
||||
// settled on the next tick of the event loop.
|
||||
input: "new Promise(function () {})",
|
||||
output: 'Promise { <state>: "pending" }',
|
||||
printOutput: "[object Promise]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Promise"
|
||||
},
|
||||
|
||||
// 10
|
||||
{
|
||||
input: "(function () { var p = new Promise(function () {}); p.foo = 1; return p; }())",
|
||||
output: 'Promise { <state>: "pending", foo: 1 }',
|
||||
printOutput: "[object Promise]",
|
||||
inspectable: true,
|
||||
variablesViewLabel: "Promise"
|
||||
}
|
||||
];
|
||||
|
||||
function test() {
|
||||
|
@ -946,6 +946,8 @@ function openDebugger(aOptions = {})
|
||||
*/
|
||||
function waitForMessages(aOptions)
|
||||
{
|
||||
info("Waiting for messages...");
|
||||
|
||||
gPendingOutputTest++;
|
||||
let webconsole = aOptions.webconsole;
|
||||
let rules = WebConsoleUtils.cloneObject(aOptions.messages, true);
|
||||
@ -1473,6 +1475,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function* checkConsoleLog(entry)
|
||||
{
|
||||
info("Logging: " + entry.input);
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute("console.log(" + entry.input + ")");
|
||||
|
||||
@ -1494,6 +1497,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function checkPrintOutput(entry)
|
||||
{
|
||||
info("Printing: " + entry.input);
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute("print(" + entry.input + ")");
|
||||
|
||||
@ -1511,6 +1515,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function* checkJSEval(entry)
|
||||
{
|
||||
info("Evaluating: " + entry.input);
|
||||
hud.jsterm.clearOutput();
|
||||
hud.jsterm.execute(entry.input);
|
||||
|
||||
@ -1534,6 +1539,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function* checkObjectClick(entry, msg)
|
||||
{
|
||||
info("Clicking: " + entry.input);
|
||||
let body = msg.querySelector(".message-body a") ||
|
||||
msg.querySelector(".message-body");
|
||||
ok(body, "the message body");
|
||||
@ -1569,6 +1575,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function checkLinkToInspector(entry, msg)
|
||||
{
|
||||
info("Checking Inspector Link: " + entry.input);
|
||||
let elementNodeWidget = [...msg._messageObject.widgets][0];
|
||||
if (!elementNodeWidget) {
|
||||
ok(!entry.inspectorIcon, "The message has no ElementNode widget");
|
||||
@ -1592,6 +1599,7 @@ function checkOutputForInputs(hud, inputTests)
|
||||
|
||||
function onVariablesViewOpen(entry, {resolve, reject}, event, view, options)
|
||||
{
|
||||
info("Variables view opened: " + entry.input);
|
||||
let label = entry.variablesViewLabel || entry.output;
|
||||
if (typeof label == "string" && options.label != label) {
|
||||
return;
|
||||
|
@ -626,7 +626,7 @@ SessionStore.prototype = {
|
||||
if (aBrowser.__SS_restore)
|
||||
return;
|
||||
|
||||
let aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
|
||||
aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
|
||||
|
||||
let tabData = {};
|
||||
tabData.entries = aHistory.entries;
|
||||
|
@ -25,8 +25,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
|
||||
"resource:///modules/BrowserUITelemetry.jsm");
|
||||
|
||||
|
||||
const UITOUR_PERMISSION = "uitour";
|
||||
const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
|
||||
const PREF_SEENPAGEIDS = "browser.uitour.seenPageIDs";
|
||||
const MAX_BUTTONS = 4;
|
||||
|
||||
@ -222,18 +220,13 @@ this.UITour = {
|
||||
JSON.stringify([...this.seenPageIDs]));
|
||||
},
|
||||
|
||||
onPageEvent: function(aEvent) {
|
||||
onPageEvent: function(aMessage, aEvent) {
|
||||
let contentDocument = null;
|
||||
if (aEvent.target instanceof Ci.nsIDOMHTMLDocument)
|
||||
contentDocument = aEvent.target;
|
||||
else if (aEvent.target instanceof Ci.nsIDOMHTMLElement)
|
||||
contentDocument = aEvent.target.ownerDocument;
|
||||
else
|
||||
return false;
|
||||
|
||||
// Ignore events if they're not from a trusted origin.
|
||||
if (!this.ensureTrustedOrigin(contentDocument))
|
||||
return false;
|
||||
let browser = aMessage.target;
|
||||
let window = browser.ownerDocument.defaultView;
|
||||
let tab = window.gBrowser.getTabForBrowser(browser);
|
||||
let messageManager = browser.messageManager;
|
||||
|
||||
if (typeof aEvent.detail != "object")
|
||||
return false;
|
||||
@ -246,20 +239,22 @@ this.UITour = {
|
||||
if (typeof data != "object")
|
||||
return false;
|
||||
|
||||
let window = this.getChromeWindow(contentDocument);
|
||||
// Do this before bailing if there's no tab, so later we can pick up the pieces:
|
||||
window.gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
let tab = window.gBrowser._getTabForContentWindow(contentDocument.defaultView);
|
||||
if (!tab) {
|
||||
// This should only happen while detaching a tab:
|
||||
if (this._detachingTab) {
|
||||
this._queuedEvents.push(aEvent);
|
||||
this._pendingDoc = Cu.getWeakReference(contentDocument);
|
||||
|
||||
if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
|
||||
contentDocument = browser.contentWindow.document;
|
||||
if (!tab) {
|
||||
// This should only happen while detaching a tab:
|
||||
if (this._detachingTab) {
|
||||
this._queuedEvents.push(aEvent);
|
||||
this._pendingDoc = Cu.getWeakReference(contentDocument);
|
||||
return;
|
||||
}
|
||||
Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
|
||||
"This shouldn't happen!");
|
||||
return;
|
||||
}
|
||||
Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
|
||||
"This shouldn't happen!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
@ -315,7 +310,7 @@ this.UITour = {
|
||||
|
||||
let iconURL = null;
|
||||
if (typeof data.icon == "string")
|
||||
iconURL = this.resolveURL(contentDocument, data.icon);
|
||||
iconURL = this.resolveURL(browser, data.icon);
|
||||
|
||||
let buttons = [];
|
||||
if (Array.isArray(data.buttons) && data.buttons.length > 0) {
|
||||
@ -329,7 +324,7 @@ this.UITour = {
|
||||
};
|
||||
|
||||
if (typeof buttonData.icon == "string")
|
||||
button.iconURL = this.resolveURL(contentDocument, buttonData.icon);
|
||||
button.iconURL = this.resolveURL(browser, buttonData.icon);
|
||||
|
||||
if (typeof buttonData.style == "string")
|
||||
button.style = buttonData.style;
|
||||
@ -349,7 +344,7 @@ this.UITour = {
|
||||
if (typeof data.targetCallbackID == "string")
|
||||
infoOptions.targetCallbackID = data.targetCallbackID;
|
||||
|
||||
this.showInfo(contentDocument, target, data.title, data.text, iconURL, buttons, infoOptions);
|
||||
this.showInfo(messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
|
||||
}).then(null, Cu.reportError);
|
||||
break;
|
||||
}
|
||||
@ -382,7 +377,7 @@ this.UITour = {
|
||||
case "showMenu": {
|
||||
this.showMenu(window, data.name, () => {
|
||||
if (typeof data.showCallbackID == "string")
|
||||
this.sendPageCallback(contentDocument, data.showCallbackID);
|
||||
this.sendPageCallback(messageManager, data.showCallbackID);
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -428,7 +423,7 @@ this.UITour = {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.getConfiguration(contentDocument, data.configuration, data.callbackID);
|
||||
this.getConfiguration(messageManager, window, data.configuration, data.callbackID);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -450,19 +445,22 @@ this.UITour = {
|
||||
// Add a widget to the toolbar
|
||||
let targetPromise = this.getTarget(window, data.name);
|
||||
targetPromise.then(target => {
|
||||
this.addNavBarWidget(target, contentDocument, data.callbackID);
|
||||
this.addNavBarWidget(target, messageManager, data.callbackID);
|
||||
}).then(null, Cu.reportError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.originTabs.has(window))
|
||||
this.originTabs.set(window, new Set());
|
||||
if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
|
||||
if (!this.originTabs.has(window)) {
|
||||
this.originTabs.set(window, new Set());
|
||||
}
|
||||
|
||||
this.originTabs.get(window).add(tab);
|
||||
tab.addEventListener("TabClose", this);
|
||||
tab.addEventListener("TabBecomingWindow", this);
|
||||
window.addEventListener("SSWindowClosing", this);
|
||||
this.originTabs.get(window).add(tab);
|
||||
tab.addEventListener("TabClose", this);
|
||||
tab.addEventListener("TabBecomingWindow", this);
|
||||
window.addEventListener("SSWindowClosing", this);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
@ -621,44 +619,7 @@ this.UITour = {
|
||||
.wrappedJSObject;
|
||||
},
|
||||
|
||||
isTestingOrigin: function(aURI) {
|
||||
if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add any testing origins (comma-seperated) to the whitelist for the session.
|
||||
for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) {
|
||||
try {
|
||||
let testingURI = Services.io.newURI(origin, null, null);
|
||||
if (aURI.prePath == testingURI.prePath) {
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
ensureTrustedOrigin: function(aDocument) {
|
||||
if (aDocument.defaultView.top != aDocument.defaultView)
|
||||
return false;
|
||||
|
||||
let uri = aDocument.documentURIObject;
|
||||
|
||||
if (uri.schemeIs("chrome"))
|
||||
return true;
|
||||
|
||||
if (!this.isSafeScheme(uri))
|
||||
return false;
|
||||
|
||||
let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
|
||||
if (permission == Services.perms.ALLOW_ACTION)
|
||||
return true;
|
||||
|
||||
return this.isTestingOrigin(uri);
|
||||
},
|
||||
|
||||
// This function is copied to UITourListener.
|
||||
isSafeScheme: function(aURI) {
|
||||
let allowedSchemes = new Set(["https", "about"]);
|
||||
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
|
||||
@ -670,9 +631,9 @@ this.UITour = {
|
||||
return true;
|
||||
},
|
||||
|
||||
resolveURL: function(aDocument, aURL) {
|
||||
resolveURL: function(aBrowser, aURL) {
|
||||
try {
|
||||
let uri = Services.io.newURI(aURL, null, aDocument.documentURIObject);
|
||||
let uri = Services.io.newURI(aURL, null, aBrowser.currentURI);
|
||||
|
||||
if (!this.isSafeScheme(uri))
|
||||
return null;
|
||||
@ -683,16 +644,9 @@ this.UITour = {
|
||||
return null;
|
||||
},
|
||||
|
||||
sendPageCallback: function(aDocument, aCallbackID, aData = {}) {
|
||||
|
||||
sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
|
||||
let detail = {data: aData, callbackID: aCallbackID};
|
||||
detail = Cu.cloneInto(detail, aDocument.defaultView);
|
||||
let event = new aDocument.defaultView.CustomEvent("mozUITourResponse", {
|
||||
bubbles: true,
|
||||
detail: detail
|
||||
});
|
||||
|
||||
aDocument.dispatchEvent(event);
|
||||
aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
|
||||
},
|
||||
|
||||
isElementVisible: function(aElement) {
|
||||
@ -966,7 +920,7 @@ this.UITour = {
|
||||
/**
|
||||
* Show an info panel.
|
||||
*
|
||||
* @param {Document} aContentDocument
|
||||
* @param {nsIMessageSender} aMessageManager
|
||||
* @param {Node} aAnchor
|
||||
* @param {String} [aTitle=""]
|
||||
* @param {String} [aDescription=""]
|
||||
@ -975,7 +929,7 @@ this.UITour = {
|
||||
* @param {Object} [aOptions={}]
|
||||
* @param {String} [aOptions.closeButtonCallbackID]
|
||||
*/
|
||||
showInfo: function(aContentDocument, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
|
||||
showInfo: function(aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
|
||||
aButtons = [], aOptions = {}) {
|
||||
function showInfoPanel(aAnchorEl) {
|
||||
aAnchorEl.focus();
|
||||
@ -1014,7 +968,7 @@ this.UITour = {
|
||||
let callbackID = button.callbackID;
|
||||
el.addEventListener("command", event => {
|
||||
tooltip.hidePopup();
|
||||
this.sendPageCallback(aContentDocument, callbackID);
|
||||
this.sendPageCallback(aMessageManager, callbackID);
|
||||
});
|
||||
|
||||
tooltipButtons.appendChild(el);
|
||||
@ -1026,7 +980,7 @@ this.UITour = {
|
||||
let closeButtonCallback = (event) => {
|
||||
this.hideInfo(document.defaultView);
|
||||
if (aOptions && aOptions.closeButtonCallbackID)
|
||||
this.sendPageCallback(aContentDocument, aOptions.closeButtonCallbackID);
|
||||
this.sendPageCallback(aMessageManager, aOptions.closeButtonCallbackID);
|
||||
};
|
||||
tooltipClose.addEventListener("command", closeButtonCallback);
|
||||
|
||||
@ -1035,7 +989,7 @@ this.UITour = {
|
||||
target: aAnchor.targetName,
|
||||
type: event.type,
|
||||
};
|
||||
this.sendPageCallback(aContentDocument, aOptions.targetCallbackID, details);
|
||||
this.sendPageCallback(aMessageManager, aOptions.targetCallbackID, details);
|
||||
};
|
||||
if (aOptions.targetCallbackID && aAnchor.addTargetListener) {
|
||||
aAnchor.addTargetListener(document, targetCallback);
|
||||
@ -1214,13 +1168,13 @@ this.UITour = {
|
||||
aWindow.gBrowser.selectedTab = tab;
|
||||
},
|
||||
|
||||
getConfiguration: function(aContentDocument, aConfiguration, aCallbackID) {
|
||||
getConfiguration: function(aMessageManager, aWindow, aConfiguration, aCallbackID) {
|
||||
switch (aConfiguration) {
|
||||
case "availableTargets":
|
||||
this.getAvailableTargets(aContentDocument, aCallbackID);
|
||||
this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
|
||||
break;
|
||||
case "sync":
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, {
|
||||
this.sendPageCallback(aMessageManager, aCallbackID, {
|
||||
setup: Services.prefs.prefHasUserValue("services.sync.username"),
|
||||
});
|
||||
break;
|
||||
@ -1228,7 +1182,7 @@ this.UITour = {
|
||||
let props = ["defaultUpdateChannel", "version"];
|
||||
let appinfo = {};
|
||||
props.forEach(property => appinfo[property] = Services.appinfo[property]);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, appinfo);
|
||||
this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
|
||||
@ -1236,12 +1190,12 @@ this.UITour = {
|
||||
}
|
||||
},
|
||||
|
||||
getAvailableTargets: function(aContentDocument, aCallbackID) {
|
||||
getAvailableTargets: function(aMessageManager, aChromeWindow, aCallbackID) {
|
||||
Task.spawn(function*() {
|
||||
let window = this.getChromeWindow(aContentDocument);
|
||||
let window = aChromeWindow;
|
||||
let data = this.availableTargetsCache.get(window);
|
||||
if (data) {
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
this.sendPageCallback(aMessageManager, aCallbackID, data);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1268,16 +1222,16 @@ this.UITour = {
|
||||
targets: targetNames,
|
||||
};
|
||||
this.availableTargetsCache.set(window, data);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
this.sendPageCallback(aMessageManager, aCallbackID, data);
|
||||
}.bind(this)).catch(err => {
|
||||
Cu.reportError(err);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, {
|
||||
this.sendPageCallback(aMessageManager, aCallbackID, {
|
||||
targets: [],
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
addNavBarWidget: function (aTarget, aContentDocument, aCallbackID) {
|
||||
addNavBarWidget: function (aTarget, aMessageManager, aCallbackID) {
|
||||
if (aTarget.node) {
|
||||
Cu.reportError("UITour: can't add a widget already present: " + data.target);
|
||||
return;
|
||||
@ -1292,7 +1246,7 @@ this.UITour = {
|
||||
}
|
||||
|
||||
CustomizableUI.addWidgetToArea(aTarget.widgetName, CustomizableUI.AREA_NAVBAR);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID);
|
||||
this.sendPageCallback(aMessageManager, aCallbackID);
|
||||
},
|
||||
|
||||
_addAnnotationPanelMutationObserver: function(aPanelEl) {
|
||||
|
@ -81,12 +81,16 @@ let tests = [
|
||||
function test_highlight_2() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.hideHighlight();
|
||||
|
||||
waitForElementToBeHidden(highlight, test_highlight_3, "Highlight should be hidden after hideHighlight()");
|
||||
}
|
||||
function test_highlight_3() {
|
||||
is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_3, "Highlight should be shown after showHighlight()");
|
||||
waitForElementToBeVisible(highlight, test_highlight_4, "Highlight should be shown after showHighlight()");
|
||||
}
|
||||
function test_highlight_3() {
|
||||
function test_highlight_4() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("backForward");
|
||||
waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
|
||||
@ -302,33 +306,27 @@ let tests = [
|
||||
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
},
|
||||
function test_info_2(done) {
|
||||
taskify(function* test_info_2() {
|
||||
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");
|
||||
yield showInfoPromise("urlbar", "urlbar title", "urlbar text");
|
||||
|
||||
gContentAPI.showInfo("search", "search title", "search text");
|
||||
executeSoon(function() {
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
|
||||
is(title.textContent, "search title", "Popup should have correct title");
|
||||
is(desc.textContent, "search text", "Popup should have correct description text");
|
||||
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");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
yield showInfoPromise("search", "search title", "search text");
|
||||
|
||||
gContentAPI.showInfo("urlbar", "urlbar title", "urlbar text");
|
||||
},
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
|
||||
is(title.textContent, "search title", "Popup should have correct title");
|
||||
is(desc.textContent, "search text", "Popup should have correct description text");
|
||||
}),
|
||||
function test_getConfigurationVersion(done) {
|
||||
function callback(result) {
|
||||
let props = ["defaultUpdateChannel", "version"];
|
||||
@ -368,8 +366,9 @@ let tests = [
|
||||
},
|
||||
|
||||
// Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
|
||||
function cleanupMenus(done) {
|
||||
taskify(function* cleanupMenus() {
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
done();
|
||||
},
|
||||
yield shownPromise;
|
||||
}),
|
||||
];
|
||||
|
@ -56,19 +56,22 @@ let tests = [
|
||||
waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed",
|
||||
"Menu should remain open since UITour didn't open it in the first place");
|
||||
waitForElementToBeHidden(window.PanelUI.panel, () => {
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
});
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
}, "Info should move to the appMenu button");
|
||||
});
|
||||
}, "Info should be shown after showInfo() for fixed menu panel items");
|
||||
});
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
function test_pinnedTab(done) {
|
||||
taskify(function* test_pinnedTab() {
|
||||
is(UITour.pinnedTabs.get(window), null, "Should not already have a pinned tab");
|
||||
|
||||
gContentAPI.addPinnedTab();
|
||||
yield addPinnedTabPromise();
|
||||
|
||||
let tabInfo = UITour.pinnedTabs.get(window);
|
||||
isnot(tabInfo, null, "Should have recorded data about a pinned tab after addPinnedTab()");
|
||||
isnot(tabInfo.tab, null, "Should have added a pinned tab after addPinnedTab()");
|
||||
@ -76,28 +79,29 @@ let tests = [
|
||||
|
||||
let tab = tabInfo.tab;
|
||||
|
||||
gContentAPI.removePinnedTab();
|
||||
yield removePinnedTabPromise();
|
||||
isnot(gBrowser.tabs[0], tab, "First tab should not be the pinned tab");
|
||||
tabInfo = UITour.pinnedTabs.get(window);
|
||||
is(tabInfo, null, "Should not have any data about the removed pinned tab after removePinnedTab()");
|
||||
|
||||
gContentAPI.addPinnedTab();
|
||||
gContentAPI.addPinnedTab();
|
||||
gContentAPI.addPinnedTab();
|
||||
yield addPinnedTabPromise();
|
||||
yield addPinnedTabPromise();
|
||||
yield addPinnedTabPromise();
|
||||
is(gBrowser.tabs[1].pinned, false, "After multiple calls of addPinnedTab, should still only have one pinned tab");
|
||||
|
||||
done();
|
||||
},
|
||||
function test_menu(done) {
|
||||
}),
|
||||
taskify(function* test_menu() {
|
||||
let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
|
||||
ise(bookmarksMenuButton.open, false, "Menu should initially be closed");
|
||||
|
||||
ise(bookmarksMenuButton.open, false, "Menu should initially be closed");
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
ise(bookmarksMenuButton.open, true, "Menu should be shown after showMenu()");
|
||||
|
||||
yield waitForConditionPromise(() => {
|
||||
return bookmarksMenuButton.open;
|
||||
}, "Menu should be visible after showMenu()");
|
||||
|
||||
gContentAPI.hideMenu("bookmarks");
|
||||
ise(bookmarksMenuButton.open, false, "Menu should be closed after hideMenu()");
|
||||
|
||||
done();
|
||||
},
|
||||
yield waitForConditionPromise(() => {
|
||||
return !bookmarksMenuButton.open;
|
||||
}, "Menu should be hidden after hideMenu()");
|
||||
}),
|
||||
];
|
||||
|
@ -16,7 +16,7 @@ function test() {
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_info_icon(done) {
|
||||
taskify(function* test_info_icon() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
@ -27,139 +27,122 @@ let tests = [
|
||||
// window during the transition instead of the buttons in the popup.
|
||||
popup.setAttribute("animate", "false");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
yield showInfoPromise("urlbar", "a title", "some text", "image.png");
|
||||
|
||||
is(title.textContent, "a title", "Popup should have correct title");
|
||||
is(desc.textContent, "some text", "Popup should have correct description text");
|
||||
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");
|
||||
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");
|
||||
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) {
|
||||
taskify(function* test_info_buttons_1() {
|
||||
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) {
|
||||
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
|
||||
}),
|
||||
taskify(function* test_info_buttons_2() {
|
||||
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_info_close_button(done) {
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
|
||||
}),
|
||||
|
||||
taskify(function* test_info_close_button() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let closeButton = document.getElementById("UITourTooltipClose");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
|
||||
executeSoon(function() {
|
||||
is(gContentWindow.callbackResult, "closeButton", "Close button callback called");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
let infoOptions = gContentWindow.makeInfoOptions();
|
||||
gContentAPI.showInfo("urlbar", "Close me", "X marks the spot", null, null, infoOptions);
|
||||
},
|
||||
|
||||
function test_info_target_callback(done) {
|
||||
yield showInfoPromise("urlbar", "Close me", "X marks the spot", null, null, infoOptions);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "closeButton", "Close button callback called");
|
||||
}),
|
||||
|
||||
taskify(function* test_info_target_callback() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
PanelUI.show().then(() => {
|
||||
is(gContentWindow.callbackResult, "target", "target callback called");
|
||||
is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
|
||||
is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
|
||||
popup.removeAttribute("animate");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
let infoOptions = gContentWindow.makeInfoOptions();
|
||||
gContentAPI.showInfo("appMenu", "I want to know when the target is clicked", "*click*", null, null, infoOptions);
|
||||
},
|
||||
|
||||
yield showInfoPromise("appMenu", "I want to know when the target is clicked", "*click*", null, null, infoOptions);
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "target", "target callback called");
|
||||
is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
|
||||
is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
|
||||
|
||||
// Cleanup.
|
||||
yield hideInfoPromise();
|
||||
|
||||
popup.removeAttribute("animate");
|
||||
}),
|
||||
];
|
||||
|
@ -11,8 +11,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
let gContentDoc;
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
@ -23,61 +21,69 @@ function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* When tab is changed we're tearing the tour down. So the UITour client has to always be aware of this
|
||||
* fact and therefore listens to visibilitychange events.
|
||||
* In particular this scenario happens for detaching the tab (ie. moving it to a new window).
|
||||
*/
|
||||
let tests = [
|
||||
function test_move_tab_to_new_window(done) {
|
||||
let gOpenedWindow;
|
||||
taskify(function* test_move_tab_to_new_window(done) {
|
||||
let onVisibilityChange = (aEvent) => {
|
||||
if (!document.hidden && window != UITour.getChromeWindow(aEvent.target)) {
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
}
|
||||
};
|
||||
let onDOMWindowDestroyed = (aWindow, aTopic, aData) => {
|
||||
if (gOpenedWindow && aWindow == gOpenedWindow) {
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
let windowDestroyedDeferred = Promise.defer();
|
||||
let onDOMWindowDestroyed = (aWindow) => {
|
||||
if (gContentWindow && aWindow == gContentWindow) {
|
||||
Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
|
||||
done();
|
||||
}
|
||||
};
|
||||
let onBrowserDelayedStartup = (aWindow, aTopic, aData) => {
|
||||
gOpenedWindow = aWindow;
|
||||
Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
|
||||
try {
|
||||
let newWindowHighlight = gOpenedWindow.document.getElementById("UITourHighlight");
|
||||
let selectedTab = aWindow.gBrowser.selectedTab;
|
||||
is(selectedTab.linkedBrowser && selectedTab.linkedBrowser.contentDocument, gContentDoc, "Document should be selected in new window");
|
||||
ok(UITour.originTabs && UITour.originTabs.has(aWindow), "Window should be known");
|
||||
ok(UITour.originTabs.get(aWindow).has(selectedTab), "Tab should be known");
|
||||
waitForElementToBeVisible(newWindowHighlight, function checkHighlightIsThere() {
|
||||
let shownPromise = promisePanelShown(aWindow);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
shownPromise.then(() => {
|
||||
isnot(aWindow.PanelUI.panel.state, "closed", "Panel should be open");
|
||||
ok(aWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
|
||||
gContentAPI.hideHighlight();
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
gTestTab = null;
|
||||
aWindow.close();
|
||||
}).then(null, Components.utils.reportError);
|
||||
}, "Highlight should be shown in new window.");
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
ok(false, "An error occurred running UITour tab detach test.");
|
||||
} finally {
|
||||
gContentDoc.removeEventListener("visibilitychange", onVisibilityChange, false);
|
||||
Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
|
||||
windowDestroyedDeferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished", false);
|
||||
let browserStartupDeferred = Promise.defer();
|
||||
Services.obs.addObserver(function onBrowserDelayedStartup(aWindow) {
|
||||
Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
|
||||
browserStartupDeferred.resolve(aWindow);
|
||||
}, "browser-delayed-startup-finished", false);
|
||||
|
||||
// NB: we're using this rather than gContentWindow.document because the latter wouldn't
|
||||
// have an XRayWrapper, and we need to compare this to the doc we get using this method
|
||||
// later on...
|
||||
gContentDoc = gBrowser.selectedTab.linkedBrowser.contentDocument;
|
||||
gContentDoc.addEventListener("visibilitychange", onVisibilityChange, false);
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function checkForInitialHighlight() {
|
||||
gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
},
|
||||
yield elementVisiblePromise(highlight);
|
||||
|
||||
gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
|
||||
|
||||
gContentWindow = yield browserStartupDeferred.promise;
|
||||
|
||||
// This highlight should be shown thanks to the visibilitychange listener.
|
||||
let newWindowHighlight = gContentWindow.document.getElementById("UITourHighlight");
|
||||
yield elementVisiblePromise(newWindowHighlight);
|
||||
|
||||
let selectedTab = gContentWindow.gBrowser.selectedTab;
|
||||
is(selectedTab.linkedBrowser && selectedTab.linkedBrowser.contentDocument, gContentDoc, "Document should be selected in new window");
|
||||
ok(UITour.originTabs && UITour.originTabs.has(gContentWindow), "Window should be known");
|
||||
ok(UITour.originTabs.get(gContentWindow).has(selectedTab), "Tab should be known");
|
||||
|
||||
let shownPromise = promisePanelShown(gContentWindow);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
yield shownPromise;
|
||||
|
||||
isnot(gContentWindow.PanelUI.panel.state, "closed", "Panel should be open");
|
||||
ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
|
||||
gContentAPI.hideHighlight();
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
gTestTab = null;
|
||||
|
||||
Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
|
||||
gContentWindow.close();
|
||||
|
||||
yield windowDestroyedDeferred.promise;
|
||||
}),
|
||||
];
|
||||
|
||||
|
@ -66,9 +66,11 @@ let tests = [
|
||||
|
||||
done();
|
||||
},
|
||||
function test_seenPageIDs_set_1(done) {
|
||||
taskify(function* test_seenPageIDs_set_1() {
|
||||
gContentAPI.registerPageID("testpage1");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 3, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
@ -85,11 +87,12 @@ let tests = [
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
done();
|
||||
},
|
||||
function test_seenPageIDs_set_2(done) {
|
||||
}),
|
||||
taskify(function* test_seenPageIDs_set_2() {
|
||||
gContentAPI.registerPageID("testpage2");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 4, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
@ -105,6 +108,5 @@ let tests = [
|
||||
"After closing tab, bucket should be expiring");
|
||||
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
done();
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
@ -2,27 +2,51 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource:///modules/UITour.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
function waitForCondition(condition, nextTest, errorMsg) {
|
||||
var tries = 0;
|
||||
var interval = setInterval(function() {
|
||||
if (tries >= 30) {
|
||||
ok(false, errorMsg);
|
||||
moveOn();
|
||||
const SINGLE_TRY_TIMEOUT = 100;
|
||||
const NUMBER_OF_TRIES = 30;
|
||||
|
||||
function waitForConditionPromise(condition, timeoutMsg) {
|
||||
let defer = Promise.defer();
|
||||
let tries = 0;
|
||||
function checkCondition() {
|
||||
if (tries >= NUMBER_OF_TRIES) {
|
||||
defer.reject(timeoutMsg);
|
||||
}
|
||||
var conditionPassed;
|
||||
try {
|
||||
conditionPassed = condition();
|
||||
} catch (e) {
|
||||
ok(false, e + "\n" + e.stack);
|
||||
conditionPassed = false;
|
||||
return defer.reject(e);
|
||||
}
|
||||
if (conditionPassed) {
|
||||
moveOn();
|
||||
return defer.resolve();
|
||||
}
|
||||
tries++;
|
||||
}, 100);
|
||||
var moveOn = function() { clearInterval(interval); nextTest(); };
|
||||
setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
|
||||
}
|
||||
setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function waitForCondition(condition, nextTest, errorMsg) {
|
||||
waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
|
||||
ok(false, reason + (reason.stack ? "\n" + e.stack : ""));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to partially transition tests to Task.
|
||||
*/
|
||||
function taskify(fun) {
|
||||
return (done) => {
|
||||
return Task.spawn(fun).then(done, (reason) => {
|
||||
ok(false, reason);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function is_hidden(element) {
|
||||
@ -80,6 +104,14 @@ function waitForElementToBeHidden(element, nextTest, msg) {
|
||||
"Timeout waiting for invisibility: " + msg);
|
||||
}
|
||||
|
||||
function elementVisiblePromise(element, msg) {
|
||||
return waitForConditionPromise(() => is_visible(element), "Timeout waiting for visibility: " + msg);
|
||||
}
|
||||
|
||||
function elementHiddenPromise(element, msg) {
|
||||
return waitForConditionPromise(() => is_hidden(element), "Timeout waiting for invisibility: " + msg);
|
||||
}
|
||||
|
||||
function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
|
||||
waitForCondition(() => is_visible(popup) && popup.popupBoxObject.anchorNode == anchorNode,
|
||||
() => {
|
||||
@ -90,24 +122,69 @@ function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
|
||||
"Timeout waiting for popup at anchor: " + msg);
|
||||
}
|
||||
|
||||
function hideInfoPromise(...args) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
gContentAPI.hideInfo.apply(gContentAPI, args);
|
||||
return promisePanelElementHidden(window, popup);
|
||||
}
|
||||
|
||||
function showInfoPromise(...args) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
gContentAPI.showInfo.apply(gContentAPI, args);
|
||||
return promisePanelElementShown(window, popup);
|
||||
}
|
||||
|
||||
function waitForCallbackResultPromise() {
|
||||
return waitForConditionPromise(() => {
|
||||
return gContentWindow.callbackResult;
|
||||
}, "callback should be called");
|
||||
}
|
||||
|
||||
function addPinnedTabPromise() {
|
||||
gContentAPI.addPinnedTab();
|
||||
return waitForConditionPromise(() => {
|
||||
let tabInfo = UITour.pinnedTabs.get(window);
|
||||
if (!tabInfo) {
|
||||
return false;
|
||||
}
|
||||
return tabInfo.tab.pinned;
|
||||
});
|
||||
}
|
||||
|
||||
function removePinnedTabPromise() {
|
||||
gContentAPI.removePinnedTab();
|
||||
return waitForConditionPromise(() => {
|
||||
let tabInfo = UITour.pinnedTabs.get(window);
|
||||
return tabInfo == null;
|
||||
});
|
||||
}
|
||||
|
||||
function promisePanelShown(win) {
|
||||
let panelEl = win.PanelUI.panel;
|
||||
return promisePanelElementShown(win, panelEl);
|
||||
}
|
||||
|
||||
function promisePanelElementShown(win, aPanel) {
|
||||
function promisePanelElementEvent(win, aPanel, aEvent) {
|
||||
let deferred = Promise.defer();
|
||||
let timeoutId = win.setTimeout(() => {
|
||||
deferred.reject("Panel did not show within 5 seconds.");
|
||||
}, 5000);
|
||||
aPanel.addEventListener("popupshown", function onPanelOpen(e) {
|
||||
aPanel.removeEventListener("popupshown", onPanelOpen);
|
||||
aPanel.addEventListener(aEvent, function onPanelEvent(e) {
|
||||
aPanel.removeEventListener(aEvent, onPanelEvent);
|
||||
win.clearTimeout(timeoutId);
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promisePanelElementShown(win, aPanel) {
|
||||
return promisePanelElementEvent(win, aPanel, "popupshown");
|
||||
}
|
||||
|
||||
function promisePanelElementHidden(win, aPanel) {
|
||||
return promisePanelElementEvent(win, aPanel, "popuphidden");
|
||||
}
|
||||
|
||||
function is_element_hidden(element, msg) {
|
||||
isnot(element, null, "Element should not be null, when checking visibility");
|
||||
ok(is_hidden(element), msg);
|
||||
|
@ -4704,7 +4704,7 @@ window > chatbox {
|
||||
|
||||
@media (-moz-mac-lion-theme) {
|
||||
#TabsToolbar > .private-browsing-indicator {
|
||||
transform: translateY(calc(0px - var(--negative-space)));
|
||||
transform: translateY(calc(0px - var(--space-above-tabbar)));
|
||||
/* We offset by 38px for mask graphic, plus 4px to account for the
|
||||
* margin-left, which sums to 42px.
|
||||
*/
|
||||
|
@ -2379,12 +2379,22 @@ public class GeckoAppShell
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void enableNetworkNotifications() {
|
||||
GeckoNetworkManager.getInstance().enableNotifications();
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GeckoNetworkManager.getInstance().enableNotifications();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void disableNetworkNotifications() {
|
||||
GeckoNetworkManager.getInstance().disableNotifications();
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GeckoNetworkManager.getInstance().disableNotifications();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ import android.util.Log;
|
||||
public class GeckoNetworkManager extends BroadcastReceiver implements NativeEventListener {
|
||||
private static final String LOGTAG = "GeckoNetworkManager";
|
||||
|
||||
static private GeckoNetworkManager sInstance;
|
||||
private static GeckoNetworkManager sInstance;
|
||||
|
||||
public static void destroy() {
|
||||
if (sInstance != null) {
|
||||
@ -75,7 +75,7 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Wifi:Enable");
|
||||
}
|
||||
|
||||
private ConnectionType mConnectionType = ConnectionType.NONE;
|
||||
private volatile ConnectionType mConnectionType = ConnectionType.NONE;
|
||||
private final IntentFilter mNetworkFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
|
||||
// Whether the manager should be listening to Network Information changes.
|
||||
@ -88,6 +88,7 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
// The application context used for registering receivers, so
|
||||
// we can unregister them again later.
|
||||
private volatile Context mApplicationContext;
|
||||
private boolean mIsListening;
|
||||
|
||||
public static GeckoNetworkManager getInstance() {
|
||||
if (sInstance == null) {
|
||||
@ -118,14 +119,23 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
}
|
||||
|
||||
private void startListening() {
|
||||
if (mIsListening) {
|
||||
Log.w(LOGTAG, "Already started!");
|
||||
return;
|
||||
}
|
||||
|
||||
final Context appContext = mApplicationContext;
|
||||
if (appContext == null) {
|
||||
Log.w(LOGTAG, "Not registering receiver: no context!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.v(LOGTAG, "Registering receiver.");
|
||||
appContext.registerReceiver(this, mNetworkFilter);
|
||||
// registerReceiver will return null if registering fails.
|
||||
if (appContext.registerReceiver(this, mNetworkFilter) == null) {
|
||||
Log.e(LOGTAG, "Registering receiver failed");
|
||||
} else {
|
||||
mIsListening = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@ -158,7 +168,13 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsListening) {
|
||||
Log.w(LOGTAG, "Already stopped!");
|
||||
return;
|
||||
}
|
||||
|
||||
mApplicationContext.unregisterReceiver(this);
|
||||
mIsListening = false;
|
||||
}
|
||||
|
||||
private int wifiDhcpGatewayAddress() {
|
||||
@ -188,23 +204,29 @@ public class GeckoNetworkManager extends BroadcastReceiver implements NativeEven
|
||||
}
|
||||
|
||||
private void updateConnectionType() {
|
||||
ConnectionType previousConnectionType = mConnectionType;
|
||||
mConnectionType = getConnectionType();
|
||||
final ConnectionType previousConnectionType = mConnectionType;
|
||||
final ConnectionType newConnectionType = getConnectionType();
|
||||
if (newConnectionType == previousConnectionType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mConnectionType == previousConnectionType || !mShouldNotify) {
|
||||
mConnectionType = newConnectionType;
|
||||
|
||||
if (!mShouldNotify) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkEvent(
|
||||
mConnectionType.value,
|
||||
mConnectionType == ConnectionType.WIFI,
|
||||
newConnectionType.value,
|
||||
newConnectionType == ConnectionType.WIFI,
|
||||
wifiDhcpGatewayAddress()));
|
||||
}
|
||||
|
||||
public double[] getCurrentInformation() {
|
||||
return new double[] { mConnectionType.value,
|
||||
(mConnectionType == ConnectionType.WIFI) ? 1.0 : 0.0,
|
||||
wifiDhcpGatewayAddress()};
|
||||
final ConnectionType connectionType = mConnectionType;
|
||||
return new double[] { connectionType.value,
|
||||
connectionType == ConnectionType.WIFI ? 1.0 : 0.0,
|
||||
wifiDhcpGatewayAddress() };
|
||||
}
|
||||
|
||||
public void enableNotifications() {
|
||||
|
@ -103,6 +103,7 @@ skip-if = android_version == "10"
|
||||
[testDeviceSearchEngine]
|
||||
[testJNI]
|
||||
# [testMozPay] # see bug 945675
|
||||
[testNetworkManager]
|
||||
[testOrderedBroadcast]
|
||||
[testOSLocale]
|
||||
[testResourceSubstitutions]
|
||||
|
11
mobile/android/base/tests/testNetworkManager.java
Normal file
11
mobile/android/base/tests/testNetworkManager.java
Normal file
@ -0,0 +1,11 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testNetworkManager extends JavascriptTest {
|
||||
public testNetworkManager() {
|
||||
super("testNetworkManager.js");
|
||||
}
|
||||
}
|
28
mobile/android/base/tests/testNetworkManager.js
Normal file
28
mobile/android/base/tests/testNetworkManager.js
Normal file
@ -0,0 +1,28 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function ok(passed, text) {
|
||||
do_report_result(passed, text, Components.stack.caller, false);
|
||||
}
|
||||
|
||||
add_test(function check_linktype() {
|
||||
// Let's exercise the interface. Even if the network is not up, we can make sure nothing blows up.
|
||||
let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
|
||||
if (network.isLinkUp) {
|
||||
ok(network.linkType != Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN, "LinkType is not UNKNOWN");
|
||||
} else {
|
||||
ok(network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN, "LinkType is UNKNOWN");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
run_next_test();
|
@ -64,6 +64,7 @@ let loaderModules = {
|
||||
"toolkit/loader": loader,
|
||||
"xpcInspector": xpcInspector,
|
||||
"promise": promise,
|
||||
"PromiseDebugging": PromiseDebugging
|
||||
};
|
||||
try {
|
||||
let { indexedDB } = Cu.Sandbox(this, {wantGlobalProperties:["indexedDB"]});
|
||||
|
@ -14,6 +14,7 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dbg_assert, dumpn, update } = DevToolsUtils;
|
||||
const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
|
||||
const promise = require("promise");
|
||||
const PromiseDebugging = require("PromiseDebugging");
|
||||
const Debugger = require("Debugger");
|
||||
const xpcInspector = require("xpcInspector");
|
||||
const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
|
||||
@ -33,6 +34,33 @@ let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
// collections, etc.
|
||||
let OBJECT_PREVIEW_MAX_ITEMS = 10;
|
||||
|
||||
/**
|
||||
* Call PromiseDebugging.getState on this Debugger.Object's referent and wrap
|
||||
* the resulting `value` or `reason` properties in a Debugger.Object instance.
|
||||
*
|
||||
* See dom/webidl/PromiseDebugging.webidl
|
||||
*
|
||||
* @returns Object
|
||||
* An object of one of the following forms:
|
||||
* - { state: "pending" }
|
||||
* - { state: "fulfilled", value }
|
||||
* - { state: "rejected", reason }
|
||||
*/
|
||||
Debugger.Object.prototype.getPromiseState = function () {
|
||||
if (this.class != "Promise") {
|
||||
throw new Error(
|
||||
"Can't call `getPromiseState` on `Debugger.Object`s that don't " +
|
||||
"refer to Promise objects.");
|
||||
}
|
||||
|
||||
const state = PromiseDebugging.getState(this.unsafeDereference());
|
||||
return {
|
||||
state: state.state,
|
||||
value: this.makeDebuggeeValue(state.value),
|
||||
reason: this.makeDebuggeeValue(state.reason)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* BreakpointStore objects keep track of all breakpoints that get set so that we
|
||||
* can reset them when the same script is introduced to the thread again (such
|
||||
@ -2986,8 +3014,15 @@ function stringify(aObj) {
|
||||
DevToolsUtils.reportException("stringify", error);
|
||||
return "<dead object>";
|
||||
}
|
||||
|
||||
const stringifier = stringifiers[aObj.class] || stringifiers.Object;
|
||||
return stringifier(aObj);
|
||||
|
||||
try {
|
||||
return stringifier(aObj);
|
||||
} catch (e) {
|
||||
DevToolsUtils.reportException("stringify", e);
|
||||
return "<failed to stringify object>";
|
||||
}
|
||||
}
|
||||
|
||||
// Used to prevent infinite recursion when an array is found inside itself.
|
||||
@ -3056,7 +3091,18 @@ let stringifiers = {
|
||||
return '[Exception... "' + message + '" ' +
|
||||
'code: "' + code +'" ' +
|
||||
'nsresult: "0x' + result + ' (' + name + ')"]';
|
||||
}
|
||||
},
|
||||
Promise: obj => {
|
||||
const { state, value, reason } = obj.getPromiseState();
|
||||
let statePreview = state;
|
||||
if (state != "pending") {
|
||||
const settledValue = state === "fulfilled" ? value : reason;
|
||||
statePreview += ": " + (typeof settledValue === "object" && settledValue !== null
|
||||
? stringify(settledValue)
|
||||
: settledValue);
|
||||
}
|
||||
return "Promise (" + statePreview + ")";
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3093,6 +3139,17 @@ ObjectActor.prototype = {
|
||||
};
|
||||
|
||||
if (this.obj.class != "DeadObject") {
|
||||
// Expose internal Promise state.
|
||||
if (this.obj.class == "Promise") {
|
||||
const { state, value, reason } = this.obj.getPromiseState();
|
||||
g.promiseState = { state };
|
||||
if (state == "fulfilled") {
|
||||
g.promiseState.value = this.threadActor.createValueGrip(value);
|
||||
} else if (state == "rejected") {
|
||||
g.promiseState.reason = this.threadActor.createValueGrip(reason);
|
||||
}
|
||||
}
|
||||
|
||||
let raw = this.obj.unsafeDereference();
|
||||
|
||||
// If Cu is not defined, we are running on a worker thread, where xrays
|
||||
@ -3787,6 +3844,7 @@ DebuggerServer.ObjectActorPreviewers = {
|
||||
|
||||
return true;
|
||||
}], // DOMStringMap
|
||||
|
||||
}; // DebuggerServer.ObjectActorPreviewers
|
||||
|
||||
/**
|
||||
|
@ -119,6 +119,18 @@ function test_display_string()
|
||||
{
|
||||
input: "new Proxy({}, {})",
|
||||
output: "[object Object]"
|
||||
},
|
||||
{
|
||||
input: "Promise.resolve(5)",
|
||||
output: "Promise (fulfilled: 5)"
|
||||
},
|
||||
{
|
||||
input: "Promise.reject(new Error())",
|
||||
output: "Promise (rejected: Error)"
|
||||
},
|
||||
{
|
||||
input: "new Promise(function () {})",
|
||||
output: "Promise (pending)"
|
||||
}
|
||||
];
|
||||
|
||||
|
40
toolkit/devtools/server/tests/unit/test_promise_state-01.js
Normal file
40
toolkit/devtools/server/tests/unit/test_promise_state-01.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that the preview in a Promise's grip is correct when the Promise is
|
||||
* pending.
|
||||
*/
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
const debuggee = addTestGlobal("test-promise-state");
|
||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(function() {
|
||||
attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
|
||||
Task.spawn(function* () {
|
||||
const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
|
||||
|
||||
const grip = packet.frame.environment.bindings.variables.p;
|
||||
ok(grip.value.preview);
|
||||
equal(grip.value.class, "Promise");
|
||||
equal(grip.value.promiseState.state, "pending");
|
||||
|
||||
finishClient(client);
|
||||
});
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function evalCode(debuggee) {
|
||||
Components.utils.evalInSandbox(
|
||||
"doTest();\n" +
|
||||
function doTest() {
|
||||
var p = new Promise(function () {});
|
||||
debugger;
|
||||
},
|
||||
debuggee
|
||||
);
|
||||
}
|
45
toolkit/devtools/server/tests/unit/test_promise_state-02.js
Normal file
45
toolkit/devtools/server/tests/unit/test_promise_state-02.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that the preview in a Promise's grip is correct when the Promise is
|
||||
* fulfilled.
|
||||
*/
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
const debuggee = addTestGlobal("test-promise-state");
|
||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(function() {
|
||||
attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
|
||||
Task.spawn(function* () {
|
||||
const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
|
||||
|
||||
const grip = packet.frame.environment.bindings.variables.p;
|
||||
ok(grip.value.preview);
|
||||
equal(grip.value.class, "Promise");
|
||||
equal(grip.value.promiseState.state, "fulfilled");
|
||||
equal(grip.value.promiseState.value.actorID, packet.frame.arguments[0].actorID,
|
||||
"The promise's fulfilled state value should be the same value passed to the then function");
|
||||
|
||||
finishClient(client);
|
||||
});
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function evalCode(debuggee) {
|
||||
Components.utils.evalInSandbox(
|
||||
"doTest();\n" +
|
||||
function doTest() {
|
||||
var resolved = Promise.resolve({});
|
||||
resolved.then(() => {
|
||||
var p = resolved;
|
||||
debugger;
|
||||
});
|
||||
},
|
||||
debuggee
|
||||
);
|
||||
}
|
45
toolkit/devtools/server/tests/unit/test_promise_state-03.js
Normal file
45
toolkit/devtools/server/tests/unit/test_promise_state-03.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that the preview in a Promise's grip is correct when the Promise is
|
||||
* rejected.
|
||||
*/
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
const debuggee = addTestGlobal("test-promise-state");
|
||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(function() {
|
||||
attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
|
||||
Task.spawn(function* () {
|
||||
const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
|
||||
|
||||
const grip = packet.frame.environment.bindings.variables.p;
|
||||
ok(grip.value.preview);
|
||||
equal(grip.value.class, "Promise");
|
||||
equal(grip.value.promiseState.state, "rejected");
|
||||
equal(grip.value.promiseState.reason.actorID, packet.frame.arguments[0].actorID,
|
||||
"The promise's rejected state reason should be the same value passed to the then function");
|
||||
|
||||
finishClient(client);
|
||||
});
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function evalCode(debuggee) {
|
||||
Components.utils.evalInSandbox(
|
||||
"doTest();\n" +
|
||||
function doTest() {
|
||||
var resolved = Promise.reject(new Error("uh oh"));
|
||||
resolved.then(null, () => {
|
||||
var p = resolved;
|
||||
debugger;
|
||||
});
|
||||
},
|
||||
debuggee
|
||||
);
|
||||
}
|
@ -157,6 +157,9 @@ reason = bug 820380
|
||||
[test_objectgrips-11.js]
|
||||
[test_objectgrips-12.js]
|
||||
[test_objectgrips-13.js]
|
||||
[test_promise_state-01.js]
|
||||
[test_promise_state-02.js]
|
||||
[test_promise_state-03.js]
|
||||
[test_interrupt.js]
|
||||
[test_stepping-01.js]
|
||||
[test_stepping-02.js]
|
||||
|
@ -320,7 +320,8 @@ if (typeof Components === "object") {
|
||||
"promise": Promise,
|
||||
"Debugger": Debugger,
|
||||
"xpcInspector": xpcInspector,
|
||||
"Timer": Object.create(Timer)
|
||||
"Timer": Object.create(Timer),
|
||||
"PromiseDebugging": PromiseDebugging
|
||||
},
|
||||
paths: {
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
|
Loading…
Reference in New Issue
Block a user