Bug 1458376 - Use a <xul:browser> to display the PaymentRequest UI to fix <select> dropdowns. r=jaws draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 10 May 2018 22:05:04 -0700
changeset 795561 98bdde63ee1bcc1ffcdd8db6cd61962edefb6348
parent 795560 02c2bfaff6d4038d3e45549b7b1b429b984092a9
push id110013
push usermozilla@noorenberghe.ca
push dateWed, 16 May 2018 03:33:50 +0000
reviewersjaws
bugs1458376
milestone62.0a1
Bug 1458376 - Use a <xul:browser> to display the PaymentRequest UI to fix <select> dropdowns. r=jaws This fixes <select> dropdowns and also gets rid of the thin bottom gray line in the dialog. MozReview-Commit-ID: I7v0mVjAnQZ
browser/components/payments/content/paymentDialogWrapper.js
browser/components/payments/content/paymentDialogWrapper.xhtml
browser/components/payments/content/paymentDialogWrapper.xul
browser/components/payments/docs/index.rst
browser/components/payments/jar.mn
browser/components/payments/paymentUIService.js
browser/components/payments/test/browser/browser.ini
browser/components/payments/test/browser/browser_dropdowns.js
--- a/browser/components/payments/content/paymentDialogWrapper.js
+++ b/browser/components/payments/content/paymentDialogWrapper.js
@@ -7,16 +7,17 @@
  * own scope.
  */
 
 "use strict";
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
                      .getService(Ci.nsIPaymentRequestService);
 
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
@@ -154,17 +155,20 @@ var paymentDialogWrapper = {
     if (!this.request) {
       throw new Error(`PaymentRequest not found: ${requestId}`);
     }
 
     this.frame = frame;
     this.mm = frame.frameLoader.messageManager;
     this.mm.addMessageListener("paymentContentToChrome", this);
     this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
-    this.frame.src = "resource://payments/paymentRequest.xhtml";
+    if (AppConstants.platform == "win") {
+      this.frame.setAttribute("selectmenulist", "ContentSelectDropdown-windows");
+    }
+    this.frame.loadURI("resource://payments/paymentRequest.xhtml");
   },
 
   createShowResponse({
     acceptStatus,
     methodName = "",
     methodData = null,
     payerName = "",
     payerEmail = "",
rename from browser/components/payments/content/paymentDialogWrapper.xhtml
rename to browser/components/payments/content/paymentDialogWrapper.xul
--- a/browser/components/payments/content/paymentDialogWrapper.xhtml
+++ b/browser/components/payments/content/paymentDialogWrapper.xul
@@ -1,21 +1,44 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 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/. -->
-<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-  <title></title>
-  <link rel="stylesheet" href="chrome://payments/content/paymentDialogWrapper.css"/>
-</head>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://payments/content/paymentDialogWrapper.css"?>
+
+<!DOCTYPE window>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <popupset>
+    <!-- for select dropdowns. The menupopup is what shows the list of options,
+         and the popuponly menulist makes things like the menuactive attributes
+         work correctly on the menupopup. ContentSelectDropdown expects the
+         popuponly menulist to be its immediate parent. -->
+    <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
+      <menupopup rolluponmousewheel="true"
+                 activateontab="true" position="after_start"
+                 level="parent"
+                 />
+    </menulist>
 
-<body>
-  <iframe type="content"
-          id="paymentRequestFrame"
-          mozbrowser="true"
-          remote="true"
-          height="400"
-          width="700"></iframe>
-  <script src="chrome://payments/content/paymentDialogWrapper.js"></script>
-</body>
-</html>
+    <!-- same as above but with additional attributes for Windows -->
+    <menulist popuponly="true" id="ContentSelectDropdown-windows" hidden="true">
+      <menupopup rolluponmousewheel="true"
+                 activateontab="true" position="after_start"
+                 level="parent"
+                 consumeoutsideclicks="false" ignorekeys="shortcuts"
+                 />
+    </menulist>
+  </popupset>
+
+  <browser type="content"
+           id="paymentRequestFrame"
+           disablehistory="true"
+           nodefaultsrc="true"
+           remote="true"
+           selectmenulist="ContentSelectDropdown"
+           style="height:400px;width:700px"
+           transparent="true"></browser>
+  <script type="application/javascript" src="chrome://payments/content/paymentDialogWrapper.js"></script>
+</window>
--- a/browser/components/payments/docs/index.rst
+++ b/browser/components/payments/docs/index.rst
@@ -53,17 +53,17 @@ Communication with the DOM
 
 Communication from the DOM to the UI happens via the `paymentUIService.js` (implementing ``nsIPaymentUIService``).
 The UI talks to the DOM code via the ``nsIPaymentRequestService`` interface.
 
 
 Dialog Architecture
 ===================
 
-Privileged wrapper XHTML document (paymentDialogWrapper.xhtml) containing a remote ``<iframe mozbrowser="true" remote="true">`` containing unprivileged XHTML (paymentRequest.xhtml).
+Privileged wrapper XUL document (paymentDialogWrapper.xul) containing a remote ``<xul:browser="true" remote="true">`` containing unprivileged XHTML (paymentRequest.xhtml).
 Keeping the dialog contents unprivileged is useful since the dialog will render payment line items and shipping options that are provided by web developers and should therefore be considered untrusted.
 In order to communicate across the process boundary a privileged frame script (`paymentDialogFrameScript.js`) is loaded into the iframe to relay messages.
 This is because the unprivileged document cannot access message managers.
 Instead, all communication across the privileged/unprivileged boundary is done via custom DOM events:
 
 * A ``paymentContentToChrome`` event is dispatched when the dialog contents want to communicate with the privileged dialog wrapper.
 * A ``paymentChromeToContent`` event is dispatched on the ``window`` with the ``detail`` property populated when the privileged dialog wrapper communicates with the unprivileged dialog.
 
--- a/browser/components/payments/jar.mn
+++ b/browser/components/payments/jar.mn
@@ -2,17 +2,17 @@
 # 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/.
 
 browser.jar:
 %   content payments %content/payments/
     content/payments/paymentDialogFrameScript.js      (content/paymentDialogFrameScript.js)
     content/payments/paymentDialogWrapper.css         (content/paymentDialogWrapper.css)
     content/payments/paymentDialogWrapper.js          (content/paymentDialogWrapper.js)
-    content/payments/paymentDialogWrapper.xhtml       (content/paymentDialogWrapper.xhtml)
+    content/payments/paymentDialogWrapper.xul         (content/paymentDialogWrapper.xul)
 
 %   resource payments %res/payments/
     res/payments                                      (res/paymentRequest.*)
     res/payments/components/                          (res/components/*.css)
     res/payments/components/                          (res/components/*.js)
     res/payments/containers/                          (res/containers/*.js)
     res/payments/containers/                          (res/containers/*.css)
     res/payments/debugging.css                        (res/debugging.css)
--- a/browser/components/payments/paymentUIService.js
+++ b/browser/components/payments/paymentUIService.js
@@ -33,17 +33,17 @@ function PaymentUIService() {
     });
   });
   this.log.debug("constructor");
 }
 
 PaymentUIService.prototype = {
   classID: Components.ID("{01f8bd55-9017-438b-85ec-7c15d2b35cdc}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIPaymentUIService]),
-  DIALOG_URL: "chrome://payments/content/paymentDialogWrapper.xhtml",
+  DIALOG_URL: "chrome://payments/content/paymentDialogWrapper.xul",
   REQUEST_ID_PREFIX: "paymentRequest-",
 
   // nsIPaymentUIService implementation:
 
   showPayment(requestId) {
     this.log.debug("showPayment:", requestId);
     let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
     chromeWindow.openDialog(`${this.DIALOG_URL}?requestId=${requestId}`,
--- a/browser/components/payments/test/browser/browser.ini
+++ b/browser/components/payments/test/browser/browser.ini
@@ -4,16 +4,17 @@ prefs =
   dom.payments.request.enabled=true
 skip-if = !e10s # Bug 1365964 - Payment Request isn't implemented for non-e10s
 support-files =
   blank_page.html
 
 [browser_address_edit.js]
 [browser_card_edit.js]
 [browser_change_shipping.js]
+[browser_dropdowns.js]
 [browser_host_name.js]
 [browser_payments_onboarding_wizard.js]
 [browser_profile_storage.js]
 [browser_request_serialization.js]
 [browser_request_shipping.js]
 [browser_request_summary.js]
 uses-unsafe-cpows = true
 [browser_shippingaddresschange_error.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/payments/test/browser/browser_dropdowns.js
@@ -0,0 +1,51 @@
+"use strict";
+
+add_task(async function test_dropdown() {
+  await addSampleAddressesAndBasicCard();
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    let {win, frame} = await setupPaymentDialog(browser, {
+      details: PTU.Details.total60USD,
+      methodData: [PTU.MethodData.basicCard],
+      merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+    });
+
+    let popupset = frame.ownerDocument.querySelector("popupset");
+    ok(popupset, "popupset exists");
+    let popupshownPromise = BrowserTestUtils.waitForEvent(popupset, "popupshown");
+
+    info("switch to the address add page");
+    await spawnPaymentDialogTask(frame, async function changeToAddressAddPage() {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      let addLink = content.document.querySelector("address-picker a");
+      is(addLink.textContent, "Add", "Add link text");
+
+      addLink.click();
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "address-page" && !state.page.guid;
+      }, "Check add page state");
+    });
+
+    info("going to open the country <select>");
+    await BrowserTestUtils.synthesizeMouseAtCenter("#country", {}, frame);
+
+    let event = await popupshownPromise;
+    let expectedPopupID = "ContentSelectDropdown";
+    if (AppConstants.platform == "win") {
+      expectedPopupID = "ContentSelectDropdown-windows";
+    }
+    is(event.target.parentElement.id, expectedPopupID, "Checked menulist of opened popup");
+
+    info("clicking cancel");
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+  });
+});