Skip to content

Commit

Permalink
Bug 1512048 - Convert tabmodalprompt binding to JSM module r=Gijs
Browse files Browse the repository at this point in the history
This converts the tabmodalprompt binding to a class, to be constructed along side with the element
by TabModalPromptBox.

TabModalPromptBox will keep the instances in a map and pass it to the callers, instead of the element.
The tests and callers can access the class instance by passing the element reference to the map.

Differential Revision: https://phabricator.services.mozilla.com/D15505

--HG--
rename : toolkit/components/prompts/content/tabprompts.xml => toolkit/components/prompts/content/tabprompts.jsm
extra : moz-landing-system : lando
  • Loading branch information
timdream committed Jan 4, 2019
1 parent b89dfc0 commit bd38b39
Show file tree
Hide file tree
Showing 27 changed files with 400 additions and 410 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

this.check = function showAlert_finalCheck(aEvent) {
var dialog = aEvent.accessible.DOMNode;
var info = dialog.ui.infoBody;
var info = dialog.querySelector(".tabmodalprompt-infoBody");
testRelation(info, RELATION_DESCRIPTION_FOR, dialog);
testRelation(dialog, RELATION_DESCRIBED_BY, info);
};
Expand Down
30 changes: 20 additions & 10 deletions browser/base/content/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
SiteDataManager: "resource:///modules/SiteDataManager.jsm",
SitePermissions: "resource:///modules/SitePermissions.jsm",
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
Translation: "resource:///modules/translation/Translation.jsm",
Expand Down Expand Up @@ -8014,6 +8015,14 @@ const SafeBrowsingNotificationBox = {

function TabModalPromptBox(browser) {
this._weakBrowserRef = Cu.getWeakReference(browser);
/*
* This WeakMap holds the TabModalPrompt instances, key to the <tabmodalprompt> prompt
* in the DOM. We don't want to hold the instances directly to avoid leaking.
*
* WeakMap also prevents us from reading back its insertion order.
* Order of the elements in the DOM should be the only order to consider.
*/
this.prompts = new WeakMap();
}

TabModalPromptBox.prototype = {
Expand All @@ -8027,17 +8036,17 @@ TabModalPromptBox.prototype = {
},

appendPrompt(args, onCloseCallback) {
let newPrompt = document.createXULElement("tabmodalprompt");
let newPrompt = new TabModalPrompt(window);
this.prompts.set(newPrompt.element, newPrompt);

let browser = this.browser;
browser.parentNode.insertBefore(newPrompt, browser.nextElementSibling);
browser.parentNode.insertBefore(newPrompt.element, browser.nextElementSibling);
browser.setAttribute("tabmodalPromptShowing", true);

newPrompt.clientTop; // style flush to assure binding is attached

let prompts = this.listPrompts();
if (prompts.length > 1) {
// Let's hide ourself behind the current prompt.
newPrompt.hidden = true;
newPrompt.element.hidden = true;
}

let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
Expand All @@ -8057,7 +8066,7 @@ TabModalPromptBox.prototype = {
[hostForAllowFocusCheckbox], 1);
allowFocusCheckbox.setAttribute("label", label);
allowFocusRow.appendChild(allowFocusCheckbox);
newPrompt.appendChild(allowFocusRow);
newPrompt.ui.rows.append(allowFocusRow);
}

let tab = gBrowser.getTabForBrowser(browser);
Expand All @@ -8068,13 +8077,14 @@ TabModalPromptBox.prototype = {
},

removePrompt(aPrompt) {
this.prompts.delete(aPrompt.element);
let browser = this.browser;
browser.parentNode.removeChild(aPrompt);
aPrompt.element.remove();

let prompts = this.listPrompts();
if (prompts.length) {
let prompt = prompts[prompts.length - 1];
prompt.hidden = false;
prompt.element.hidden = false;
prompt.Dialog.setDefaultFocus();
} else {
browser.removeAttribute("tabmodalPromptShowing");
Expand All @@ -8083,10 +8093,10 @@ TabModalPromptBox.prototype = {
},

listPrompts(aPrompt) {
// Get the nodelist, then return as an array
// Get the nodelist, then return the TabModalPrompt instances as an array
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let els = this.browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
return Array.from(els);
return Array.from(els).map(el => this.prompts.get(el));
},

onNextPromptShowAllowFocusCheckboxFor(principal) {
Expand Down
2 changes: 1 addition & 1 deletion browser/base/content/tabbrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ window._gBrowser = {

// If there's a tabmodal prompt showing, focus it.
if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
let prompts = newBrowser.parentNode.getElementsByTagNameNS(this._XUL_NS, "tabmodalprompt");
let prompts = newBrowser.tabModalPromptBox.listPrompts();
let prompt = prompts[prompts.length - 1];
// @tabmodalPromptShowing is also set for other tab modal prompts
// (e.g. the Payment Request dialog) so there may not be a <tabmodalprompt>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ function onTabModalDialogLoaded(node) {
expectingDialog = false;
if (wantToClose) {
// This accepts the dialog, closing it
node.Dialog.ui.button0.click();
node.querySelector(".tabmodalprompt-button0").click();
} else {
// This keeps the page open
node.Dialog.ui.button1.click();
node.querySelector(".tabmodalprompt-button1").click();
}
if (resolveDialogPromise) {
resolveDialogPromise();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ add_task(async function() {
ok(!dialogNode.parentNode, "onbeforeunload dialog should be gone.");
if (dialogNode.parentNode) {
// Failed to remove onbeforeunload dialog, so do it ourselves:
let leaveBtn = dialogNode.ui.button0;
let leaveBtn = dialogNode.querySelector(".tabmodalprompt-button0");
waitForDialogDestroyed(dialogNode, doCompletion);
EventUtils.synthesizeMouseAtCenter(leaveBtn, {});
return;
Expand Down
21 changes: 11 additions & 10 deletions browser/base/content/test/tabPrompts/browser_multiplePrompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,22 @@ add_task(async function() {

await promptsOpenedPromise;

let promptsCount = PROMPTCOUNT;
while (promptsCount--) {
let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(prompts.length, promptsCount + 1, "There should be " + (promptsCount + 1) + " prompt(s).");
let promptElementsCount = PROMPTCOUNT;
while (promptElementsCount--) {
let promptElements = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(promptElements.length, promptElementsCount + 1, "There should be " + (promptElementsCount + 1) + " prompt(s).");
// The oldest should be the first.
let i = 0;
for (let prompt of prompts) {
for (let promptElement of promptElements) {
let prompt = tab.linkedBrowser.tabModalPromptBox.prompts.get(promptElement);
is(prompt.Dialog.args.text, "Alert countdown #" + i, "The #" + i + " alert should be labelled as such.");
if (i !== promptsCount) {
is(prompt.hidden, true, "This prompt should be hidden.");
if (i !== promptElementsCount) {
is(prompt.element.hidden, true, "This prompt should be hidden.");
i++;
continue;
}

is(prompt.hidden, false, "The last prompt should not be hidden.");
is(prompt.element.hidden, false, "The last prompt should not be hidden.");
prompt.onButtonClick(0);

// The click is handled async; wait for an event loop turn for that to
Expand All @@ -65,8 +66,8 @@ add_task(async function() {
}
}

let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(prompts.length, 0, "Prompts should all be dismissed.");
let promptElements = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(promptElements.length, 0, "Prompts should all be dismissed.");

BrowserTestUtils.removeTab(tab);
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@ add_task(async function() {
// switch tab back, and check the checkbox is displayed:
await BrowserTestUtils.switchTab(gBrowser, openedTab);
// check the prompt is there, and the extra row is present
let prompts = openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(prompts.length, 1, "There should be 1 prompt");
let ourPrompt = prompts[0];
let row = ourPrompt.querySelector("row");
ok(row, "Should have found the row with our checkbox");
let checkbox = row.querySelector("checkbox[label*='example.com']");
let promptElements = openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
is(promptElements.length, 1, "There should be 1 prompt");
let ourPromptElement = promptElements[0];
let checkbox = ourPromptElement.querySelector("checkbox[label*='example.com']");
ok(checkbox, "The checkbox should be there");
ok(!checkbox.checked, "Checkbox shouldn't be checked");
// tick box and accept dialog
checkbox.checked = true;
let ourPrompt = openedTab.linkedBrowser.tabModalPromptBox.prompts.get(ourPromptElement);
ourPrompt.onButtonClick(0);
// Wait for that click to actually be handled completely.
await new Promise(function(resolve) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ add_task(async function test_tab_options_modals() {
Assert.equal(dialogs.length, 1, "Expect a tab modal opened for the about addons tab");

info("Close the tab modal prompt");
dialogs[0].onButtonClick(0);
dialogs[0].querySelector(".tabmodalprompt-button0").click();

await extension.awaitFinish("options-ui-modals");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ function promiseAlertDialogObserved() {
Services.obs.removeObserver(observer, "common-dialog-loaded");
Services.obs.removeObserver(observer, "tabmodal-dialog-loaded");

subject.Dialog.ui.button0.click();
if (subject.Dialog) {
subject.Dialog.ui.button0.click();
} else {
subject.querySelector(".tabmodalprompt-button0").click();
}
resolve();
}
Services.obs.addObserver(observer, "common-dialog-loaded");
Expand Down
2 changes: 1 addition & 1 deletion devtools/server/tests/browser/browser_navigateEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function waitForOnBeforeUnloadDialog(browser, callback) {
const stack = browser.parentNode;
const dialogs = stack.getElementsByTagName("tabmodalprompt");
await waitUntil(() => dialogs[0]);
const {button0, button1} = dialogs[0].ui;
const {button0, button1} = browser.tabModalPromptBox.prompts.get(dialogs[0]).ui;
callback(button0, button1);
}, {capture: true, once: true});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ add_task(async function test() {
mutations.forEach(function(mutation) {
if (buttonId && mutation.type == "attributes" && browser.hasAttribute("tabmodalPromptShowing")) {
let prompt = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt")[0];
document.getAnonymousElementByAttribute(prompt, "anonid", buttonId).click();
prompt.querySelector(`.tabmodalprompt-${buttonId}`).click();
promptShown = true;
}
});
Expand Down
2 changes: 1 addition & 1 deletion docshell/test/browser/browser_onbeforeunload_navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function onTabModalDialogLoaded(node) {
observer.observe(node.parentNode, {childList: true});

BrowserTestUtils.waitForMessage(mm, "test-beforeunload:dialog-response").then((stayingOnPage) => {
let button = stayingOnPage ? node.ui.button1 : node.ui.button0;
let button = node.querySelector(stayingOnPage ? ".tabmodalprompt-button1" : ".tabmodalprompt-button0");
// ... and then actually make the dialog go away
info("Clicking button: " + button.label);
EventUtils.synthesizeMouseAtCenter(button, {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ function awaitAndCloseBeforeUnloadDialog(doStayOnPage) {
return new Promise(resolve => {
function onDialogShown(node) {
Services.obs.removeObserver(onDialogShown, "tabmodal-dialog-loaded");
let button = doStayOnPage ? node.ui.button1 : node.ui.button0;
let button =
node.querySelector(doStayOnPage ? ".tabmodalprompt-button1" : ".tabmodalprompt-button0");
button.click();
resolve();
}
Expand Down
2 changes: 1 addition & 1 deletion dom/tests/browser/browser_cancel_keydown_keypress_event.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function awaitAndCloseAlertDialog() {
return new Promise(resolve => {
function onDialogShown(node) {
Services.obs.removeObserver(onDialogShown, "tabmodal-dialog-loaded");
let button = node.ui.button0;
let button = node.querySelector(".tabmodalprompt-button0");
button.click();
resolve();
}
Expand Down
2 changes: 1 addition & 1 deletion dom/tests/browser/browser_test_focus_after_modal_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function awaitAndClosePrompt() {
return new Promise(resolve => {
function onDialogShown(node) {
Services.obs.removeObserver(onDialogShown, "tabmodal-dialog-loaded");
let button = node.ui.button0;
let button = node.querySelector(".tabmodalprompt-button0");
button.click();
resolve();
}
Expand Down
12 changes: 10 additions & 2 deletions editor/libeditor/tests/test_bug569988.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@
os.addObserver(onPromptLoad, "tabmodal-dialog-loaded");

function onPromptLoad(subject, topic, data) {
let ui = subject.Dialog ? subject.Dialog.ui : undefined;
if (!ui) {
// subject is an tab prompt, find the elements ourselves
ui = {
loginTextbox: subject.querySelector(".tabmodalprompt-loginTextbox"),
button0: subject.querySelector(".tabmodalprompt-button0"),
};
}
sendAsyncMessage("ok", [true, "onPromptLoad is called"]);
gPromptInput = subject.Dialog.ui.loginTextbox;
gPromptInput = ui.loginTextbox;
gPromptInput.addEventListener("focus", onPromptFocus);
// shift focus to ensure it fires.
subject.Dialog.ui.button0.focus();
ui.button0.focus();
gPromptInput.focus();
}

Expand Down
2 changes: 1 addition & 1 deletion layout/base/tests/browser_disableDialogs_onbeforeunload.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ add_task(async function enableDialogs() {
let dialogShown = false;
function onDialogShown(node) {
dialogShown = true;
let dismissButton = node.ui.button0;
let dismissButton = node.querySelector(".tabmodalprompt-button0");
dismissButton.click();
}
let obsName = "tabmodal-dialog-loaded";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ add_task(async function doClick() {
let dialogShown = false;
function onDialogShown(node) {
dialogShown = true;
let dismissButton = node.ui.button0;
let dismissButton = node.querySelector(".tabmodalprompt-button0");
dismissButton.click();
}
let obsName = "tabmodal-dialog-loaded";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ add_task(async function doClick() {
let dialogShown = false;
function onDialogShown(node) {
dialogShown = true;
let dismissButton = node.ui.button0;
let dismissButton = node.querySelector(".tabmodalprompt-button0");
dismissButton.click();
}
let obsName = "tabmodal-dialog-loaded";
Expand Down
5 changes: 3 additions & 2 deletions testing/marionette/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ browser.Context = class {

// The modal is a direct sibling of the browser element.
// See tabbrowser.xml's getTabModalPromptBox.
let modals = br.parentNode.getElementsByTagNameNS(
let modalElements = br.parentNode.getElementsByTagNameNS(
XUL_NS, "tabmodalprompt");
return modals[0].ui;

return br.tabModalPromptBox.prompts.get(modalElements[0]).ui;
}

/**
Expand Down
Loading

0 comments on commit bd38b39

Please sign in to comment.