"use strict"; const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm"); /* global UIState */ const lastModifiedFixture = 1507655615.87; // Approx Oct 10th 2017 const mockTargets = [ { id: "0", name: "foo", type: "phone", clientRecord: { id: "cli0", serverLastModified: lastModifiedFixture, type: "phone", }, }, { id: "1", name: "bar", type: "desktop", clientRecord: { id: "cli1", serverLastModified: lastModifiedFixture, type: "desktop", }, }, { id: "2", name: "baz", type: "phone", clientRecord: { id: "cli2", serverLastModified: lastModifiedFixture, type: "phone", }, }, { id: "3", name: "no client record device", type: "phone" }, ]; add_task(async function openPanel() { if (AppConstants.platform == "macosx") { // Ignore this test on Mac. return; } let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Should still open the panel when Ctrl key is pressed. await promisePageActionPanelOpen({ ctrlKey: true }); // Done. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; }); }); add_task(async function starButtonCtrlClick() { // On macOS, ctrl-click shouldn't open the panel because this normally opens // the context menu. This happens via the `contextmenu` event which is created // by widget code, so our simulated clicks do not do so, so we can't test // anything on macOS. if (AppConstants.platform == "macosx") { return; } // Open a unique page. let url = "http://example.com/browser_page_action_star_button"; await BrowserTestUtils.withNewTab(url, async () => { StarUI._createPanelIfNeeded(); // The button ignores activation while the bookmarked status is being // updated. So, wait for it to finish updating. await TestUtils.waitForCondition( () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING ); const popup = document.getElementById("editBookmarkPanel"); const starButtonBox = document.getElementById("star-button-box"); let shownPromise = promisePanelShown(popup); EventUtils.synthesizeMouseAtCenter(starButtonBox, { ctrlKey: true }); await shownPromise; ok(true, "Panel shown after button pressed"); let hiddenPromise = promisePanelHidden(popup); document.getElementById("editBookmarkPanelRemoveButton").click(); await hiddenPromise; }); }); add_task(async function bookmark() { // Open a unique page. let url = "http://example.com/browser_page_action_menu"; await BrowserTestUtils.withNewTab(url, async () => { // Open the panel. await promisePageActionPanelOpen(); // The bookmark button should read "Bookmark This Page" and not be starred. let bookmarkButton = document.getElementById("pageAction-panel-bookmark"); Assert.equal(bookmarkButton.label, "Bookmark This Page"); Assert.ok(!bookmarkButton.hasAttribute("starred")); // Click the button. let hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}); await hiddenPromise; // Make sure the edit-bookmark panel opens, then hide it. await new Promise(resolve => { if (StarUI.panel.state == "open") { resolve(); return; } StarUI.panel.addEventListener("popupshown", resolve, { once: true }); }); Assert.equal( BookmarkingUI.starBox.getAttribute("open"), "true", "Star has open attribute" ); StarUI.panel.hidePopup(); Assert.ok( !BookmarkingUI.starBox.hasAttribute("open"), "Star no longer has open attribute" ); // Open the panel again. await promisePageActionPanelOpen(); // The bookmark button should now read "Edit This Bookmark" and be starred. Assert.equal(bookmarkButton.label, "Edit This Bookmark"); Assert.ok(bookmarkButton.hasAttribute("starred")); Assert.equal(bookmarkButton.getAttribute("starred"), "true"); // Click it again. hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}); await hiddenPromise; // The edit-bookmark panel should open again. await new Promise(resolve => { if (StarUI.panel.state == "open") { resolve(); return; } StarUI.panel.addEventListener("popupshown", resolve, { once: true }); }); let onItemRemovedPromise = PlacesTestUtils.waitForNotification( "bookmark-removed", events => events.some(event => event.url == url), "places" ); // Click the remove-bookmark button in the panel. StarUI._element("editBookmarkPanelRemoveButton").click(); // Wait for the bookmark to be removed before continuing. await onItemRemovedPromise; // Open the panel again. await promisePageActionPanelOpen(); // The bookmark button should read "Bookmark This Page" and not be starred. Assert.equal(bookmarkButton.label, "Bookmark This Page"); Assert.ok(!bookmarkButton.hasAttribute("starred")); // Done. hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; }); }); add_task(async function pinTabFromPanel() { // Open an actionable page so that the main page action button appears. (It // does not appear on about:blank for example.) let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Open the panel and click Pin Tab. await promisePageActionPanelOpen(); let pinTabButton = document.getElementById("pageAction-panel-pinTab"); Assert.equal(pinTabButton.label, "Pin Tab"); let hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(pinTabButton, {}); await hiddenPromise; Assert.ok(gBrowser.selectedTab.pinned, "Tab was pinned"); // Open the panel and click Unpin Tab. await promisePageActionPanelOpen(); Assert.equal(pinTabButton.label, "Unpin Tab"); hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(pinTabButton, {}); await hiddenPromise; Assert.ok(!gBrowser.selectedTab.pinned, "Tab was unpinned"); }); }); add_task(async function pinTabFromURLBar() { // Open an actionable page so that the main page action button appears. (It // does not appear on about:blank for example.) let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Add action to URL bar. let action = PageActions._builtInActions.find(a => a.id == "pinTab"); action.pinnedToUrlbar = true; registerCleanupFunction(() => (action.pinnedToUrlbar = false)); // Click the Pin Tab button. let pinTabButton = document.getElementById("pageAction-urlbar-pinTab"); EventUtils.synthesizeMouseAtCenter(pinTabButton, {}); await TestUtils.waitForCondition( () => gBrowser.selectedTab.pinned, "Tab was pinned" ); // Click the Unpin Tab button EventUtils.synthesizeMouseAtCenter(pinTabButton, {}); await TestUtils.waitForCondition( () => !gBrowser.selectedTab.pinned, "Tab was unpinned" ); }); }); add_task(async function emailLink() { // Open an actionable page so that the main page action button appears. (It // does not appear on about:blank for example.) let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Replace the email-link entry point to check whether it's called. let originalFn = MailIntegration.sendLinkForBrowser; let fnCalled = false; MailIntegration.sendLinkForBrowser = () => { fnCalled = true; }; registerCleanupFunction(() => { MailIntegration.sendLinkForBrowser = originalFn; }); // Open the panel and click Email Link. await promisePageActionPanelOpen(); let emailLinkButton = document.getElementById("pageAction-panel-emailLink"); let hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(emailLinkButton, {}); await hiddenPromise; Assert.ok(fnCalled); }); }); add_task(async function copyURLFromPanel() { // Open an actionable page so that the main page action button appears. (It // does not appear on about:blank for example.) let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Add action to URL bar. let action = PageActions._builtInActions.find(a => a.id == "copyURL"); action.pinnedToUrlbar = true; registerCleanupFunction(() => (action.pinnedToUrlbar = false)); // Open the panel and click Copy URL. await promisePageActionPanelOpen(); Assert.ok(true, "page action panel opened"); let copyURLButton = document.getElementById("pageAction-panel-copyURL"); let hiddenPromise = promisePageActionPanelHidden(); EventUtils.synthesizeMouseAtCenter(copyURLButton, {}); await hiddenPromise; let feedbackPanel = ConfirmationHint._panel; let feedbackShownPromise = BrowserTestUtils.waitForEvent( feedbackPanel, "popupshown" ); await feedbackShownPromise; Assert.equal( feedbackPanel.anchorNode.id, "pageActionButton", "Feedback menu should be anchored on the main Page Action button" ); let feedbackHiddenPromise = promisePanelHidden(feedbackPanel); await feedbackHiddenPromise; action.pinnedToUrlbar = false; }); }); add_task(async function copyURLFromURLBar() { // Open an actionable page so that the main page action button appears. (It // does not appear on about:blank for example.) let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Add action to URL bar. let action = PageActions._builtInActions.find(a => a.id == "copyURL"); action.pinnedToUrlbar = true; registerCleanupFunction(() => (action.pinnedToUrlbar = false)); let copyURLButton = document.getElementById("pageAction-urlbar-copyURL"); let panel = ConfirmationHint._panel; let feedbackShownPromise = promisePanelShown(panel); EventUtils.synthesizeMouseAtCenter(copyURLButton, {}); await feedbackShownPromise; Assert.equal( panel.anchorNode.id, "pageAction-urlbar-copyURL", "Feedback menu should be anchored on the main URL bar button" ); let feedbackHiddenPromise = promisePanelHidden(panel); await feedbackHiddenPromise; action.pinnedToUrlbar = false; }); }); add_task(async function sendToDevice_nonSendable() { // Open a tab that's not sendable but where the page action buttons still // appear. about:about is convenient. await BrowserTestUtils.withNewTab("about:about", async () => { await promiseSyncReady(); // Open the panel. Send to Device should be disabled. await promisePageActionPanelOpen(); Assert.equal( BrowserPageActions.mainButtonNode.getAttribute("open"), "true", "Main button has 'open' attribute" ); let panelButton = BrowserPageActions.panelButtonNodeForActionID( "sendToDevice" ); Assert.equal( panelButton.disabled, true, "The panel button should be disabled" ); let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; Assert.ok( !BrowserPageActions.mainButtonNode.hasAttribute("open"), "Main button no longer has 'open' attribute" ); // The urlbar button shouldn't exist. let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID( "sendToDevice" ); Assert.equal(urlbarButton, null, "The urlbar button shouldn't exist"); }); }); add_task(async function sendToDevice_syncNotReady_other_states() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => null); sandbox .stub(UIState, "get") .returns({ status: UIState.STATUS_NOT_VERIFIED }); sandbox.stub(gSync, "isSendableURI").returns(true); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { label: "Account Not Verified", }, disabled: true, }, null, { attrs: { label: "Verify Your Account...", }, }, ]; checkSendToDeviceItems(expectedItems); // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); }); }); add_task(async function sendToDevice_syncNotReady_configured() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); const recentDeviceList = sandbox .stub(fxAccounts.device, "recentDeviceList") .get(() => null); sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox.stub(fxAccounts.device, "refreshDeviceList").callsFake(() => { recentDeviceList.get(() => mockTargets.map(({ id, name, type }) => ({ id, name, type })) ); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); }); let onShowingSubview = BrowserPageActions.sendToDevice.onShowingSubview; sandbox .stub(BrowserPageActions.sendToDevice, "onShowingSubview") .callsFake((...args) => { this.numCall++ || (this.numCall = 1); onShowingSubview.call(BrowserPageActions.sendToDevice, ...args); testSendTabToDeviceMenu(this.numCall); }); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); function testSendTabToDeviceMenu(numCall) { if (numCall == 1) { // "Syncing devices" should be shown. checkSendToDeviceItems([ { className: "pageAction-sendToDevice-notReady", disabled: true, }, ]); } else if (numCall == 2) { // The devices should be shown in the subview. let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, ]; for (let target of mockTargets) { const attrs = { clientId: target.id, label: target.name, clientType: target.type, }; if (target.clientRecord && target.clientRecord.serverLastModified) { attrs.tooltiptext = gSync.formatLastSyncDate( new Date(target.clientRecord.serverLastModified * 1000) ); } expectedItems.push({ attrs, }); } expectedItems.push(null, { attrs: { label: "Send to All Devices", }, }); expectedItems.push(null, { attrs: { label: "Manage Devices...", }, }); checkSendToDeviceItems(expectedItems); } else { ok(false, "This should never happen"); } } // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); }); }); add_task(async function sendToDevice_notSignedIn() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { label: "Not Signed In", }, disabled: true, }, null, { attrs: { label: "Sign in to Firefox...", }, }, { attrs: { label: "Learn About Sending Tabs...", }, }, ]; checkSendToDeviceItems(expectedItems); // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; }); }); add_task(async function sendToDevice_noDevices() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => []); sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox.stub(fxAccounts.device, "refreshDeviceList").resolves(true); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { label: "No Devices Connected", }, disabled: true, }, null, { attrs: { label: "Connect Another Device...", }, }, { attrs: { label: "Learn About Sending Tabs...", }, }, ]; checkSendToDeviceItems(expectedItems); // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); await UIState.reset(); }); }); add_task(async function sendToDevice_devices() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox .stub(fxAccounts.device, "recentDeviceList") .get(() => mockTargets.map(({ id, name, type }) => ({ id, name, type }))); sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox .stub(fxAccounts.commands.sendTab, "isDeviceCompatible") .returns(true); sandbox.stub(fxAccounts.device, "refreshDeviceList").resolves(true); sandbox.spy(Weave.Service, "sync"); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); // The devices should be shown in the subview. let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { clientId: "1", label: "bar", clientType: "desktop", }, }, { attrs: { clientId: "2", label: "baz", clientType: "phone", }, }, { attrs: { clientId: "0", label: "foo", clientType: "phone", }, }, { attrs: { clientId: "3", label: "no client record device", clientType: "phone", }, }, null, { attrs: { label: "Send to All Devices", }, }, { attrs: { label: "Manage Devices...", }, }, ]; checkSendToDeviceItems(expectedItems); Assert.ok(Weave.Service.sync.notCalled); // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); }); }); add_task(async function sendTabToDevice_syncEnabled() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => []); sandbox .stub(UIState, "get") .returns({ status: UIState.STATUS_SIGNED_IN, syncEnabled: true }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox.spy(fxAccounts.device, "refreshDeviceList"); sandbox.spy(Weave.Service, "sync"); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); // Click Send to Device. let viewPromise = promisePageActionViewShown(); EventUtils.synthesizeMouseAtCenter(sendToDeviceButton, {}); let view = await viewPromise; Assert.equal(view.id, "pageAction-panel-sendToDevice-subview"); let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { label: "No Devices Connected", }, disabled: true, }, null, { attrs: { label: "Connect Another Device...", }, }, { attrs: { label: "Learn About Sending Tabs...", }, }, ]; checkSendToDeviceItems(expectedItems); Assert.ok(Weave.Service.sync.notCalled); Assert.equal(fxAccounts.device.refreshDeviceList.callCount, 1); // Done, hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); }); }); add_task(async function sendToDevice_title() { // Open two tabs that are sendable. await BrowserTestUtils.withNewTab( "http://example.com/a", async otherBrowser => { await BrowserTestUtils.withNewTab("http://example.com/b", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => []); sandbox .stub(UIState, "get") .returns({ status: UIState.STATUS_SIGNED_IN }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox.stub(fxAccounts.device, "refreshDeviceList").resolves(true); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Open the panel. Only one tab is selected, so the action's title should // be "Send Tab to Device". await promisePageActionPanelOpen(); let sendToDeviceButton = document.getElementById( "pageAction-panel-sendToDevice" ); Assert.ok(!sendToDeviceButton.disabled); Assert.equal(sendToDeviceButton.label, "Send Tab to Device"); // Hide the panel. let hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; // Add the other tab to the selection. gBrowser.addToMultiSelectedTabs( gBrowser.getTabForBrowser(otherBrowser), { isLastMultiSelectChange: true } ); // Open the panel again. Now the action's title should be "Send 2 Tabs to // Device". await promisePageActionPanelOpen(); Assert.ok(!sendToDeviceButton.disabled); Assert.equal(sendToDeviceButton.label, "Send 2 Tabs to Device"); // Hide the panel. hiddenPromise = promisePageActionPanelHidden(); BrowserPageActions.panelNode.hidePopup(); await hiddenPromise; cleanUp(); await UIState.reset(); }); } ); }); add_task(async function sendToDevice_inUrlbar() { // Open a tab that's sendable. await BrowserTestUtils.withNewTab("http://example.com/", async () => { await promiseSyncReady(); const sandbox = sinon.createSandbox(); sandbox .stub(fxAccounts.device, "recentDeviceList") .get(() => mockTargets.map(({ id, name, type }) => ({ id, name, type }))); sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN }); sandbox.stub(gSync, "isSendableURI").returns(true); sandbox .stub(fxAccounts.commands.sendTab, "isDeviceCompatible") .returns(true); sandbox.stub(fxAccounts.device, "refreshDeviceList").resolves(true); sandbox .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId") .callsFake(fxaDeviceId => { let target = mockTargets.find(c => c.id == fxaDeviceId); return target ? target.clientRecord : null; }); sandbox .stub(Weave.Service.clientsEngine, "getClientType") .callsFake( id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id) .clientRecord.type ); sandbox.stub(gSync, "sendTabToDevice").resolves(true); let cleanUp = () => { sandbox.restore(); }; registerCleanupFunction(cleanUp); // Add Send to Device to the urlbar. let action = PageActions.actionForID("sendToDevice"); action.pinnedToUrlbar = true; // Click it to open its panel. let urlbarButton = document.getElementById( BrowserPageActions.urlbarButtonNodeIDForActionID(action.id) ); Assert.notEqual(urlbarButton, null, "The urlbar button should exist"); Assert.ok( !urlbarButton.disabled, "The urlbar button should not be disabled" ); EventUtils.synthesizeMouseAtCenter(urlbarButton, {}); // The panel element for _activatedActionPanelID is created synchronously // only after the associated button has been clicked. await promisePanelShown(BrowserPageActions._activatedActionPanelID); Assert.equal( urlbarButton.getAttribute("open"), "true", "Button has open attribute" ); // The devices should be shown in the subview. let expectedItems = [ { className: "pageAction-sendToDevice-notReady", display: "none", disabled: true, }, { attrs: { clientId: "1", label: "bar", clientType: "desktop", }, }, { attrs: { clientId: "2", label: "baz", clientType: "phone", }, }, { attrs: { clientId: "0", label: "foo", clientType: "phone", }, }, { attrs: { clientId: "3", label: "no client record device", clientType: "phone", }, }, null, { attrs: { label: "Send to All Devices", }, }, { attrs: { label: "Manage Devices...", }, }, ]; checkSendToDeviceItems(expectedItems, true); // Get the first device menu item in the panel. let bodyID = BrowserPageActions._panelViewNodeIDForActionID("sendToDevice", true) + "-body"; let body = document.getElementById(bodyID); let deviceMenuItem = body.querySelector(".sendtab-target"); Assert.notEqual(deviceMenuItem, null); // For good measure, wait until it's visible. let dwu = window.windowUtils; await TestUtils.waitForCondition(() => { let bounds = dwu.getBoundsWithoutFlushing(deviceMenuItem); return bounds.height > 0 && bounds.width > 0; }, "Waiting for first device menu item to appear"); // Click it, which should cause the panel to close. let hiddenPromise = promisePanelHidden( BrowserPageActions._activatedActionPanelID ); EventUtils.synthesizeMouseAtCenter(deviceMenuItem, {}); info("Waiting for Send to Device panel to close after clicking a device"); await hiddenPromise; Assert.ok( !urlbarButton.hasAttribute("open"), "URL bar button no longer has open attribute" ); // And then the "Sent!" notification panel should open and close by itself // after a moment. info("Waiting for the Sent! notification panel to open"); await promisePanelShown(ConfirmationHint._panel.id); Assert.equal(ConfirmationHint._panel.anchorNode.id, urlbarButton.id); info("Waiting for the Sent! notification panel to close"); await promisePanelHidden(ConfirmationHint._panel.id); // Remove Send to Device from the urlbar. action.pinnedToUrlbar = false; cleanUp(); }); }); add_task(async function contextMenu() { // Open an actionable page so that the main page action button appears. let url = "http://example.com/"; await BrowserTestUtils.withNewTab(url, async () => { // Open the panel and then open the context menu on the bookmark button. await promisePageActionPanelOpen(); let bookmarkButton = document.getElementById("pageAction-panel-bookmark"); let contextMenuPromise = promisePanelShown("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(bookmarkButton, { type: "contextmenu", button: 2, }); await contextMenuPromise; // The context menu should show the "remove" item. Click it. let menuItems = collectContextMenuItems(); Assert.equal(menuItems.length, 1, "Context menu has one child"); Assert.equal( menuItems[0].label, "Remove from Address Bar", "Context menu is in the 'remove' state" ); contextMenuPromise = promisePanelHidden("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(menuItems[0], {}); await contextMenuPromise; // The action should be removed from the urlbar. In this case, the bookmark // star, the node in the urlbar should be hidden. let starButtonBox = document.getElementById("star-button-box"); await TestUtils.waitForCondition(() => { return starButtonBox.hidden; }, "Waiting for star button to become hidden"); // Open the context menu again on the bookmark button. (The page action // panel remains open.) contextMenuPromise = promisePanelShown("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(bookmarkButton, { type: "contextmenu", button: 2, }); await contextMenuPromise; // The context menu should show the "add" item. Click it. menuItems = collectContextMenuItems(); Assert.equal(menuItems.length, 1, "Context menu has one child"); Assert.equal( menuItems[0].label, "Add to Address Bar", "Context menu is in the 'add' state" ); contextMenuPromise = promisePanelHidden("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(menuItems[0], {}); await contextMenuPromise; // The action should be added to the urlbar. await TestUtils.waitForCondition(() => { return !starButtonBox.hidden; }, "Waiting for star button to become unhidden"); // Open the context menu on the bookmark star in the urlbar. contextMenuPromise = promisePanelShown("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(starButtonBox, { type: "contextmenu", button: 2, }); await contextMenuPromise; // The context menu should show the "remove" item. Click it. menuItems = collectContextMenuItems(); Assert.equal(menuItems.length, 1, "Context menu has one child"); Assert.equal( menuItems[0].label, "Remove from Address Bar", "Context menu is in the 'remove' state" ); contextMenuPromise = promisePanelHidden("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(menuItems[0], {}); await contextMenuPromise; // The action should be removed from the urlbar. await TestUtils.waitForCondition(() => { return starButtonBox.hidden; }, "Waiting for star button to become hidden"); // Finally, add the bookmark star back to the urlbar so that other tests // that rely on it are OK. await promisePageActionPanelOpen(); contextMenuPromise = promisePanelShown("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(bookmarkButton, { type: "contextmenu", button: 2, }); await contextMenuPromise; menuItems = collectContextMenuItems(); Assert.equal(menuItems.length, 1, "Context menu has one child"); Assert.equal( menuItems[0].label, "Add to Address Bar", "Context menu is in the 'add' state" ); contextMenuPromise = promisePanelHidden("pageActionContextMenu"); EventUtils.synthesizeMouseAtCenter(menuItems[0], {}); await contextMenuPromise; await TestUtils.waitForCondition(() => { return !starButtonBox.hidden; }, "Waiting for star button to become unhidden"); }); // urlbar tests that run after this one can break if the mouse is left over // the area where the urlbar popup appears, which seems to happen due to the // above synthesized mouse events. Move it over the urlbar. EventUtils.synthesizeMouseAtCenter(gURLBar.textbox, { type: "mousemove" }); gURLBar.focus(); }); function promiseSyncReady() { let service = Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports) .wrappedJSObject; return service.whenLoaded().then(() => { UIState.isReady(); return UIState.refresh(); }); } function checkSendToDeviceItems(expectedItems, forUrlbar = false) { let bodyID = BrowserPageActions._panelViewNodeIDForActionID("sendToDevice", forUrlbar) + "-body"; let body = document.getElementById(bodyID); Assert.equal(body.children.length, expectedItems.length); for (let i = 0; i < expectedItems.length; i++) { let expected = expectedItems[i]; let actual = body.children[i]; if (!expected) { Assert.equal(actual.localName, "toolbarseparator"); continue; } if ("id" in expected) { Assert.equal(actual.id, expected.id); } if ("className" in expected) { let expectedNames = expected.className.split(/\s+/); for (let name of expectedNames) { Assert.ok( actual.classList.contains(name), `classList contains: ${name}` ); } } let display = "display" in expected ? expected.display : "-moz-box"; Assert.equal(getComputedStyle(actual).display, display); let disabled = "disabled" in expected ? expected.disabled : false; Assert.equal(actual.disabled, disabled); if ("attrs" in expected) { for (let name in expected.attrs) { Assert.ok(actual.hasAttribute(name)); let attrVal = actual.getAttribute(name); if (name == "label") { attrVal = attrVal.normalize("NFKC"); // There's a bug with … } Assert.equal(attrVal, expected.attrs[name]); } } } } function collectContextMenuItems() { let contextMenu = document.getElementById("pageActionContextMenu"); return Array.prototype.filter.call(contextMenu.children, node => { return window.getComputedStyle(node).visibility == "visible"; }); }