diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /devtools/client/responsive.html/test/browser | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/responsive.html/test/browser')
33 files changed, 3204 insertions, 0 deletions
diff --git a/devtools/client/responsive.html/test/browser/.eslintrc.js b/devtools/client/responsive.html/test/browser/.eslintrc.js new file mode 100644 index 000000000..698ae9181 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/responsive.html/test/browser/browser.ini b/devtools/client/responsive.html/test/browser/browser.ini new file mode 100644 index 000000000..71cf6d9b6 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser.ini @@ -0,0 +1,44 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +# !e10s: RDM only works for remote tabs +skip-if = !e10s +support-files = + devices.json + doc_page_state.html + geolocation.html + head.js + touch.html + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + !/devtools/client/framework/test/shared-redux-head.js + !/devtools/client/inspector/test/shared-head.js + !/devtools/client/shared/test/test-actor.js + !/devtools/client/shared/test/test-actor-registry.js + +[browser_device_change.js] +[browser_device_modal_error.js] +[browser_device_modal_exit.js] +[browser_device_modal_submit.js] +[browser_device_width.js] +[browser_dpr_change.js] +[browser_exit_button.js] +[browser_frame_script_active.js] +[browser_menu_item_01.js] +[browser_menu_item_02.js] +[browser_mouse_resize.js] +[browser_navigation.js] +[browser_network_throttling.js] +[browser_page_state.js] +[browser_permission_doorhanger.js] +[browser_resize_cmd.js] +[browser_screenshot_button.js] +[browser_tab_close.js] +[browser_tab_remoteness_change.js] +[browser_toolbox_computed_view.js] +[browser_toolbox_rule_view.js] +[browser_toolbox_swap_browsers.js] +[browser_touch_device.js] +[browser_touch_simulation.js] +[browser_viewport_basics.js] +[browser_window_close.js] diff --git a/devtools/client/responsive.html/test/browser/browser_device_change.js b/devtools/client/responsive.html/test/browser/browser_device_change.js new file mode 100644 index 000000000..b88f73522 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_device_change.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests changing viewport device +const TEST_URL = "data:text/html;charset=utf-8,Device list test"; + +const DEFAULT_DPPX = window.devicePixelRatio; +const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"] + .getService(Ci.nsIHttpProtocolHandler) + .userAgent; + +const Types = require("devtools/client/responsive.html/types"); + +const testDevice = { + "name": "Fake Phone RDM Test", + "width": 320, + "height": 570, + "pixelRatio": 5.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "custom", + "featured": true, +}; + +// Add the new device to the list +addDeviceForTest(testDevice); + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + let { store } = ui.toolWindow; + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); + + // Test defaults + testViewportDimensions(ui, 320, 480); + yield testUserAgent(ui, DEFAULT_UA); + yield testDevicePixelRatio(ui, DEFAULT_DPPX); + yield testTouchEventsOverride(ui, false); + testViewportDeviceSelectLabel(ui, "no device selected"); + + // Test device with custom properties + yield selectDevice(ui, "Fake Phone RDM Test"); + yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height); + yield testUserAgent(ui, testDevice.userAgent); + yield testDevicePixelRatio(ui, testDevice.pixelRatio); + yield testTouchEventsOverride(ui, true); + + // Test resetting device when resizing viewport + let deviceRemoved = once(ui, "device-removed"); + yield testViewportResize(ui, ".viewport-vertical-resize-handle", + [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui); + yield deviceRemoved; + yield testUserAgent(ui, DEFAULT_UA); + yield testDevicePixelRatio(ui, DEFAULT_DPPX); + yield testTouchEventsOverride(ui, false); + testViewportDeviceSelectLabel(ui, "no device selected"); + + // Test device with generic properties + yield selectDevice(ui, "Laptop (1366 x 768)"); + yield waitForViewportResizeTo(ui, 1366, 768); + yield testUserAgent(ui, DEFAULT_UA); + yield testDevicePixelRatio(ui, 1); + yield testTouchEventsOverride(ui, false); +}); + +function testViewportDimensions(ui, w, h) { + let viewport = ui.toolWindow.document.querySelector(".viewport-content"); + + is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"), + `${w}px`, `Viewport should have width of ${w}px`); + is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"), + `${h}px`, `Viewport should have height of ${h}px`); +} + +function* testUserAgent(ui, expected) { + let ua = yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + return content.navigator.userAgent; + }); + is(ua, expected, `UA should be set to ${expected}`); +} + +function* testDevicePixelRatio(ui, expected) { + let dppx = yield getViewportDevicePixelRatio(ui); + is(dppx, expected, `devicePixelRatio should be set to ${expected}`); +} + +function* getViewportDevicePixelRatio(ui) { + return yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + return content.devicePixelRatio; + }); +} diff --git a/devtools/client/responsive.html/test/browser/browser_device_modal_error.js b/devtools/client/responsive.html/test/browser/browser_device_modal_error.js new file mode 100644 index 000000000..d9308eb6c --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_device_modal_error.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test to check that RDM can handle properly an error in the device list + +const TEST_URL = "data:text/html;charset=utf-8,"; +const Types = require("devtools/client/responsive.html/types"); +const { getStr } = require("devtools/client/responsive.html/utils/l10n"); + +// Set a wrong URL for the device list file +add_task(function* () { + yield SpecialPowers.pushPrefEnv({ + set: [["devtools.devices.url", TEST_URI_ROOT + "wrong_devices_file.json"]], + }); +}); + +addRDMTask(TEST_URL, function* ({ ui }) { + let { store, document } = ui.toolWindow; + let select = document.querySelector(".viewport-device-selector"); + + // Wait until the viewport has been added and the device list state indicates + // an error + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.ERROR); + + // The device selector placeholder should be set accordingly + let placeholder = select.options[select.selectedIndex].innerHTML; + ok(placeholder == getStr("responsive.deviceListError"), + "Device selector indicates an error"); + + // The device selector should be disabled + ok(select.disabled, "Device selector is disabled"); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js new file mode 100644 index 000000000..30d057ebe --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test submitting display device changes on the device modal + +const TEST_URL = "data:text/html;charset=utf-8,"; +const Types = require("devtools/client/responsive.html/types"); + +addRDMTask(TEST_URL, function* ({ ui }) { + let { store, document } = ui.toolWindow; + let modal = document.querySelector("#device-modal-wrapper"); + let closeButton = document.querySelector("#device-close-button"); + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); + + openDeviceModal(ui); + + let preferredDevicesBefore = _loadPreferredDevices(); + + info("Check the first unchecked device and exit the modal."); + let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => !cb.checked)[0]; + let value = uncheckedCb.value; + uncheckedCb.click(); + closeButton.click(); + + ok(modal.classList.contains("closed") && !modal.classList.contains("opened"), + "The device modal is closed on exit."); + + info("Check that the device list remains unchanged after exitting."); + let preferredDevicesAfter = _loadPreferredDevices(); + + is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size, + "Got expected number of added devices."); + + is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size, + "Got expected number of removed devices."); + + ok(!preferredDevicesAfter.removed.has(value), + value + " was not added to removed device list."); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js new file mode 100644 index 000000000..90f364ce7 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test submitting display device changes on the device modal +const { getDevices } = require("devtools/client/shared/devices"); + +const addedDevice = { + "name": "Fake Phone RDM Test", + "width": 320, + "height": 570, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": false, + "os": "custom", + "featured": true, +}; + +const TEST_URL = "data:text/html;charset=utf-8,"; +const Types = require("devtools/client/responsive.html/types"); + +addRDMTask(TEST_URL, function* ({ ui }) { + let { store, document } = ui.toolWindow; + let modal = document.querySelector("#device-modal-wrapper"); + let select = document.querySelector(".viewport-device-selector"); + let submitButton = document.querySelector("#device-submit-button"); + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); + + openDeviceModal(ui); + + info("Checking displayed device checkboxes are checked in the device modal."); + let checkedCbs = [...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => cb.checked); + + let remoteList = yield getDevices(); + + let featuredCount = remoteList.TYPES.reduce((total, type) => { + return total + remoteList[type].reduce((subtotal, device) => { + return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0); + }, 0); + }, 0); + + is(featuredCount, checkedCbs.length, + "Got expected number of displayed devices."); + + for (let cb of checkedCbs) { + ok(Object.keys(remoteList).filter(type => remoteList[type][cb.value]), + cb.value + " is correctly checked."); + } + + // Tests where the user adds a non-featured device + info("Check the first unchecked device and submit new device list."); + let uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => !cb.checked)[0]; + let value = uncheckedCb.value; + uncheckedCb.click(); + submitButton.click(); + + ok(modal.classList.contains("closed") && !modal.classList.contains("opened"), + "The device modal is closed on submit."); + + info("Checking that the new device is added to the user preference list."); + let preferredDevices = _loadPreferredDevices(); + ok(preferredDevices.added.has(value), value + " in user added list."); + + info("Checking new device is added to the device selector."); + let options = [...select.options]; + is(options.length - 2, featuredCount + 1, + "Got expected number of devices in device selector."); + ok(options.filter(o => o.value === value)[0], + value + " added to the device selector."); + + info("Reopen device modal and check new device is correctly checked"); + openDeviceModal(ui); + ok([...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => cb.checked && cb.value === value)[0], + value + " is checked in the device modal."); + + // Tests where the user removes a featured device + info("Uncheck the first checked device different than the previous one"); + let checkedCb = [...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => cb.checked && cb.value != value)[0]; + let checkedVal = checkedCb.value; + checkedCb.click(); + submitButton.click(); + + info("Checking that the device is removed from the user preference list."); + preferredDevices = _loadPreferredDevices(); + ok(preferredDevices.removed.has(checkedVal), checkedVal + " in removed list"); + + info("Checking that the device is not in the device selector."); + options = [...select.options]; + is(options.length - 2, featuredCount, + "Got expected number of devices in device selector."); + ok(!options.filter(o => o.value === checkedVal)[0], + checkedVal + " removed from the device selector."); + + info("Reopen device modal and check device is correctly unchecked"); + openDeviceModal(ui); + ok([...document.querySelectorAll(".device-input-checkbox")] + .filter(cb => !cb.checked && cb.value === checkedVal)[0], + checkedVal + " is unchecked in the device modal."); + + // Let's add a dummy device to simulate featured flag changes for next test + addDeviceForTest(addedDevice); +}); + +addRDMTask(TEST_URL, function* ({ ui }) { + let { store, document } = ui.toolWindow; + let select = document.querySelector(".viewport-device-selector"); + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); + + openDeviceModal(ui); + + let remoteList = yield getDevices(); + let featuredCount = remoteList.TYPES.reduce((total, type) => { + return total + remoteList[type].reduce((subtotal, device) => { + return subtotal + ((device.os != "fxos" && device.featured) ? 1 : 0); + }, 0); + }, 0); + let preferredDevices = _loadPreferredDevices(); + + // Tests to prove that reloading the RDM didn't break our device list + info("Checking new featured device appears in the device selector."); + let options = [...select.options]; + is(options.length - 2, featuredCount + - preferredDevices.removed.size + preferredDevices.added.size, + "Got expected number of devices in device selector."); + + ok(options.filter(o => o.value === addedDevice.name)[0], + "dummy device added to the device selector."); + + ok(options.filter(o => preferredDevices.added.has(o.value))[0], + "device added by user still in the device selector."); + + ok(!options.filter(o => preferredDevices.removed.has(o.value))[0], + "device removed by user not in the device selector."); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_device_width.js b/devtools/client/responsive.html/test/browser/browser_device_width.js new file mode 100644 index 000000000..9489d8f0b --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_device_width.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = "data:text/html;charset=utf-8,"; + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + ok(ui, "An instance of the RDM should be attached to the tab."); + yield setViewportSize(ui, manager, 110, 500); + + info("Checking initial width/height properties."); + yield doInitialChecks(ui); + + info("Changing the RDM size"); + yield setViewportSize(ui, manager, 90, 500); + + info("Checking for screen props"); + yield checkScreenProps(ui); + + info("Setting docShell.deviceSizeIsPageSize to false"); + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + docShell.deviceSizeIsPageSize = false; + }); + + info("Checking for screen props once again."); + yield checkScreenProps2(ui); +}); + +function* doInitialChecks(ui) { + let { innerWidth, matchesMedia } = yield grabContentInfo(ui); + is(innerWidth, 110, "initial width should be 110px"); + ok(!matchesMedia, "media query shouldn't match."); +} + +function* checkScreenProps(ui) { + let { matchesMedia, screen } = yield grabContentInfo(ui); + ok(matchesMedia, "media query should match"); + isnot(window.screen.width, screen.width, + "screen.width should not be the size of the screen."); + is(screen.width, 90, "screen.width should be the page width"); + is(screen.height, 500, "screen.height should be the page height"); +} + +function* checkScreenProps2(ui) { + let { matchesMedia, screen } = yield grabContentInfo(ui); + ok(!matchesMedia, "media query should be re-evaluated."); + is(window.screen.width, screen.width, + "screen.width should be the size of the screen."); +} + +function grabContentInfo(ui) { + return ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + return { + screen: { + width: content.screen.width, + height: content.screen.height + }, + innerWidth: content.innerWidth, + matchesMedia: content.matchMedia("(max-device-width:100px)").matches + }; + }); +} diff --git a/devtools/client/responsive.html/test/browser/browser_dpr_change.js b/devtools/client/responsive.html/test/browser/browser_dpr_change.js new file mode 100644 index 000000000..4c70087bf --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_dpr_change.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests changing viewport DPR +const TEST_URL = "data:text/html;charset=utf-8,DPR list test"; +const DEFAULT_DPPX = window.devicePixelRatio; +const VIEWPORT_DPPX = DEFAULT_DPPX + 2; +const Types = require("devtools/client/responsive.html/types"); + +const testDevice = { + "name": "Fake Phone RDM Test", + "width": 320, + "height": 470, + "pixelRatio": 5.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "custom", + "featured": true, +}; + +// Add the new device to the list +addDeviceForTest(testDevice); + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + yield waitStartup(ui); + + yield testDefaults(ui); + yield testChangingDevice(ui); + yield testResetWhenResizingViewport(ui); + yield testChangingDPR(ui); +}); + +function* waitStartup(ui) { + let { store } = ui.toolWindow; + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); +} + +function* testDefaults(ui) { + info("Test Defaults"); + + yield testDevicePixelRatio(ui, window.devicePixelRatio); + testViewportDPRSelect(ui, {value: window.devicePixelRatio, disabled: false}); + testViewportDeviceSelectLabel(ui, "no device selected"); +} + +function* testChangingDevice(ui) { + info("Test Changing Device"); + + let waitPixelRatioChange = onceDevicePixelRatioChange(ui); + + yield selectDevice(ui, testDevice.name); + yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height); + yield waitPixelRatioChange; + yield testDevicePixelRatio(ui, testDevice.pixelRatio); + testViewportDPRSelect(ui, {value: testDevice.pixelRatio, disabled: true}); + testViewportDeviceSelectLabel(ui, testDevice.name); +} + +function* testResetWhenResizingViewport(ui) { + info("Test reset when resizing the viewport"); + + let waitPixelRatioChange = onceDevicePixelRatioChange(ui); + + let deviceRemoved = once(ui, "device-removed"); + yield testViewportResize(ui, ".viewport-vertical-resize-handle", + [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui); + yield deviceRemoved; + + yield waitPixelRatioChange; + yield testDevicePixelRatio(ui, window.devicePixelRatio); + + testViewportDPRSelect(ui, {value: window.devicePixelRatio, disabled: false}); + testViewportDeviceSelectLabel(ui, "no device selected"); +} + +function* testChangingDPR(ui) { + info("Test changing device pixel ratio"); + + let waitPixelRatioChange = onceDevicePixelRatioChange(ui); + + yield selectDPR(ui, VIEWPORT_DPPX); + yield waitPixelRatioChange; + yield testDevicePixelRatio(ui, VIEWPORT_DPPX); + testViewportDPRSelect(ui, {value: VIEWPORT_DPPX, disabled: false}); + testViewportDeviceSelectLabel(ui, "no device selected"); +} + +function testViewportDPRSelect(ui, expected) { + info("Test viewport's DPR Select"); + + let select = ui.toolWindow.document.querySelector("#global-dpr-selector > select"); + is(select.value, expected.value, + `DPR Select value should be: ${expected.value}`); + is(select.disabled, expected.disabled, + `DPR Select should be ${expected.disabled ? "disabled" : "enabled"}.`); +} + +function* testDevicePixelRatio(ui, expected) { + info("Test device pixel ratio"); + + let dppx = yield getViewportDevicePixelRatio(ui); + is(dppx, expected, `devicePixelRatio should be: ${expected}`); +} + +function* getViewportDevicePixelRatio(ui) { + return yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + return content.devicePixelRatio; + }); +} + +function onceDevicePixelRatioChange(ui) { + return ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + info(`Listening for a pixel ratio change (current: ${content.devicePixelRatio}dppx)`); + + let pixelRatio = content.devicePixelRatio; + let mql = content.matchMedia(`(resolution: ${pixelRatio}dppx)`); + + return new Promise(resolve => { + const onWindowCreated = () => { + if (pixelRatio !== content.devicePixelRatio) { + resolve(); + } + }; + + addEventListener("DOMWindowCreated", onWindowCreated, {once: true}); + + mql.addListener(function listener() { + mql.removeListener(listener); + removeEventListener("DOMWindowCreated", onWindowCreated, {once: true}); + resolve(); + }); + }); + }); +} diff --git a/devtools/client/responsive.html/test/browser/browser_exit_button.js b/devtools/client/responsive.html/test/browser/browser_exit_button.js new file mode 100644 index 000000000..62e652274 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_exit_button.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = "data:text/html;charset=utf-8,"; + +// Test global exit button +addRDMTask(TEST_URL, function* (...args) { + yield testExitButton(...args); +}); + +// Test global exit button on detached tab. +// See Bug 1262806 +add_task(function* () { + let tab = yield addTab(TEST_URL); + let { ui, manager } = yield openRDM(tab); + + yield waitBootstrap(ui); + + let waitTabIsDetached = Promise.all([ + once(tab, "TabClose"), + once(tab.linkedBrowser, "SwapDocShells") + ]); + + // Detach the tab with RDM open. + let newWindow = gBrowser.replaceTabWithWindow(tab); + + // Waiting the tab is detached. + yield waitTabIsDetached; + + // Get the new tab instance. + tab = newWindow.gBrowser.tabs[0]; + + // Detaching a tab closes RDM. + ok(!manager.isActiveForTab(tab), + "Responsive Design Mode is not active for the tab"); + + // Reopen the RDM and test the exit button again. + yield testExitButton(yield openRDM(tab)); + yield BrowserTestUtils.closeWindow(newWindow); +}); + +function* waitBootstrap(ui) { + let { toolWindow, tab } = ui; + let { store } = toolWindow; + let url = String(tab.linkedBrowser.currentURI.spec); + + // Wait until the viewport has been added. + yield waitUntilState(store, state => state.viewports.length == 1); + + // Wait until the document has been loaded. + yield waitForFrameLoad(ui, url); +} + +function* testExitButton({ui, manager}) { + yield waitBootstrap(ui); + + let exitButton = ui.toolWindow.document.getElementById("global-exit-button"); + + ok(manager.isActiveForTab(ui.tab), + "Responsive Design Mode active for the tab"); + + exitButton.click(); + + yield once(manager, "off"); + + ok(!manager.isActiveForTab(ui.tab), + "Responsive Design Mode is not active for the tab"); +} diff --git a/devtools/client/responsive.html/test/browser/browser_frame_script_active.js b/devtools/client/responsive.html/test/browser/browser_frame_script_active.js new file mode 100644 index 000000000..81449a340 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_frame_script_active.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Verify frame script is active when expected. + +const e10s = require("devtools/client/responsive.html/utils/e10s"); + +const TEST_URL = "http://example.com/"; +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + + let mm = ui.getViewportBrowser().messageManager; + let { active } = yield e10s.request(mm, "IsActive"); + is(active, true, "Frame script is active"); + + yield closeRDM(tab); + + // Must re-get the messageManager on each run since it changes when RDM opens + // or closes due to the design of swapFrameLoaders. Also, we only have access + // to a valid `ui` instance while RDM is open. + mm = tab.linkedBrowser.messageManager; + ({ active } = yield e10s.request(mm, "IsActive")); + is(active, false, "Frame script is active"); + + // Try another round as well to be sure there is no state saved anywhere + ({ ui } = yield openRDM(tab)); + + mm = ui.getViewportBrowser().messageManager; + ({ active } = yield e10s.request(mm, "IsActive")); + is(active, true, "Frame script is active"); + + yield closeRDM(tab); + + // Must re-get the messageManager on each run since it changes when RDM opens + // or closes due to the design of swapFrameLoaders. Also, we only have access + // to a valid `ui` instance while RDM is open. + mm = tab.linkedBrowser.messageManager; + ({ active } = yield e10s.request(mm, "IsActive")); + is(active, false, "Frame script is active"); + + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_menu_item_01.js b/devtools/client/responsive.html/test/browser/browser_menu_item_01.js new file mode 100644 index 000000000..8e1c1c4cd --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_menu_item_01.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test RDM menu item is checked when expected, on multiple tabs. + +const TEST_URL = "data:text/html;charset=utf-8,"; + +const tabUtils = require("sdk/tabs/utils"); +const { startup } = require("sdk/window/helpers"); + +const activateTab = (tab) => new Promise(resolve => { + let { tabContainer } = tabUtils.getOwnerWindow(tab).gBrowser; + + tabContainer.addEventListener("TabSelect", function listener({type}) { + tabContainer.removeEventListener(type, listener); + resolve(); + }); + + tabUtils.activateTab(tab); +}); + +const isMenuChecked = () => { + let menu = document.getElementById("menu_responsiveUI"); + return menu.getAttribute("checked") === "true"; +}; + +add_task(function* () { + yield startup(window); + + ok(!isMenuChecked(), + "RDM menu item is unchecked by default"); + + const tab = yield addTab(TEST_URL); + + ok(!isMenuChecked(), + "RDM menu item is unchecked for new tab"); + + yield openRDM(tab); + + ok(isMenuChecked(), + "RDM menu item is checked with RDM open"); + + const tab2 = yield addTab(TEST_URL); + + ok(!isMenuChecked(), + "RDM menu item is unchecked for new tab"); + + yield activateTab(tab); + + ok(isMenuChecked(), + "RDM menu item is checked for the tab where RDM is open"); + + yield closeRDM(tab); + + ok(!isMenuChecked(), + "RDM menu item is unchecked after RDM is closed"); + + yield removeTab(tab); + yield removeTab(tab2); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_menu_item_02.js b/devtools/client/responsive.html/test/browser/browser_menu_item_02.js new file mode 100644 index 000000000..166ecb8ae --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_menu_item_02.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test RDM menu item is checked when expected, on multiple windows. + +const TEST_URL = "data:text/html;charset=utf-8,"; + +const { getMostRecentBrowserWindow } = require("sdk/window/utils"); + +const isMenuCheckedFor = ({document}) => { + let menu = document.getElementById("menu_responsiveUI"); + return menu.getAttribute("checked") === "true"; +}; + +add_task(function* () { + const window1 = yield BrowserTestUtils.openNewBrowserWindow(); + let { gBrowser } = window1; + + yield BrowserTestUtils.withNewTab({ gBrowser, url: TEST_URL }, + function* (browser) { + let tab = gBrowser.getTabForBrowser(browser); + + is(window1, getMostRecentBrowserWindow(), + "The new window is the active one"); + + ok(!isMenuCheckedFor(window1), + "RDM menu item is unchecked by default"); + + yield openRDM(tab); + + ok(isMenuCheckedFor(window1), + "RDM menu item is checked with RDM open"); + + yield closeRDM(tab); + + ok(!isMenuCheckedFor(window1), + "RDM menu item is unchecked with RDM closed"); + }); + + yield BrowserTestUtils.closeWindow(window1); + + is(window, getMostRecentBrowserWindow(), + "The original window is the active one"); + + ok(!isMenuCheckedFor(window), + "RDM menu item is unchecked"); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_mouse_resize.js b/devtools/client/responsive.html/test/browser/browser_mouse_resize.js new file mode 100644 index 000000000..98ccdab69 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_mouse_resize.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = "data:text/html;charset=utf-8,"; + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + let store = ui.toolWindow.store; + + // Wait until the viewport has been added + yield waitUntilState(store, state => state.viewports.length == 1); + + yield setViewportSize(ui, manager, 300, 300); + + // Do horizontal + vertical resize + yield testViewportResize(ui, ".viewport-resize-handle", + [10, 10], [320, 310], [10, 10]); + + // Do horizontal resize + yield testViewportResize(ui, ".viewport-horizontal-resize-handle", + [-10, 10], [300, 310], [-10, 0]); + + // Do vertical resize + yield testViewportResize(ui, ".viewport-vertical-resize-handle", + [-10, -10], [300, 300], [0, -10], ui); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_navigation.js b/devtools/client/responsive.html/test/browser/browser_navigation.js new file mode 100644 index 000000000..2c9f0027f --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_navigation.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the primary browser navigation UI to verify it's connected to the viewport. + +const DUMMY_1_URL = "http://example.com/"; +const TEST_URL = `${URL_ROOT}doc_page_state.html`; +const DUMMY_2_URL = "http://example.com/browser/"; +const DUMMY_3_URL = "http://example.com/browser/devtools/"; + +add_task(function* () { + // Load up a sequence of pages: + // 0. DUMMY_1_URL + // 1. TEST_URL + // 2. DUMMY_2_URL + let tab = yield addTab(DUMMY_1_URL); + let browser = tab.linkedBrowser; + yield load(browser, TEST_URL); + yield load(browser, DUMMY_2_URL); + + // Check session history state + let history = yield getSessionHistory(browser); + is(history.index, 2, "At page 2 in history"); + is(history.entries.length, 3, "3 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, TEST_URL, "Page 1 URL matches"); + is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches"); + + // Go back one so we're at the test page + yield back(browser); + + // Check session history state + history = yield getSessionHistory(browser); + is(history.index, 1, "At page 1 in history"); + is(history.entries.length, 3, "3 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, TEST_URL, "Page 1 URL matches"); + is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches"); + + yield openRDM(tab); + + ok(browser.webNavigation.canGoBack, "Going back is allowed"); + ok(browser.webNavigation.canGoForward, "Going forward is allowed"); + is(browser.documentURI.spec, TEST_URL, "documentURI matches page 1"); + is(browser.contentTitle, "Page State Test", "contentTitle matches page 1"); + + yield forward(browser); + + ok(browser.webNavigation.canGoBack, "Going back is allowed"); + ok(!browser.webNavigation.canGoForward, "Going forward is not allowed"); + is(browser.documentURI.spec, DUMMY_2_URL, "documentURI matches page 2"); + is(browser.contentTitle, "mochitest index /browser/", "contentTitle matches page 2"); + + yield back(browser); + yield back(browser); + + ok(!browser.webNavigation.canGoBack, "Going back is not allowed"); + ok(browser.webNavigation.canGoForward, "Going forward is allowed"); + is(browser.documentURI.spec, DUMMY_1_URL, "documentURI matches page 0"); + is(browser.contentTitle, "mochitest index /", "contentTitle matches page 0"); + + let receivedStatusChanges = new Promise(resolve => { + let statusChangesSeen = 0; + let statusChangesExpected = 2; + let progressListener = { + onStatusChange(webProgress, request, status, message) { + info(message); + if (++statusChangesSeen == statusChangesExpected) { + gBrowser.removeProgressListener(progressListener); + ok(true, `${statusChangesExpected} status changes while loading`); + resolve(); + } + } + }; + gBrowser.addProgressListener(progressListener); + }); + yield load(browser, DUMMY_3_URL); + yield receivedStatusChanges; + + ok(browser.webNavigation.canGoBack, "Going back is allowed"); + ok(!browser.webNavigation.canGoForward, "Going forward is not allowed"); + is(browser.documentURI.spec, DUMMY_3_URL, "documentURI matches page 3"); + is(browser.contentTitle, "mochitest index /browser/devtools/", + "contentTitle matches page 3"); + + yield closeRDM(tab); + + // Check session history state + history = yield getSessionHistory(browser); + is(history.index, 1, "At page 1 in history"); + is(history.entries.length, 2, "2 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, DUMMY_3_URL, "Page 1 URL matches"); + + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_network_throttling.js b/devtools/client/responsive.html/test/browser/browser_network_throttling.js new file mode 100644 index 000000000..18c4a90ed --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_network_throttling.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles"); + +// Tests changing network throttling +const TEST_URL = "data:text/html;charset=utf-8,Network throttling test"; + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + let { store } = ui.toolWindow; + + // Wait until the viewport has been added + yield waitUntilState(store, state => state.viewports.length == 1); + + // Test defaults + testNetworkThrottlingSelectorLabel(ui, "No throttling"); + yield testNetworkThrottlingState(ui, null); + + // Test a fast profile + yield testThrottlingProfile(ui, "Wi-Fi"); + + // Test a slower profile + yield testThrottlingProfile(ui, "Regular 3G"); + + // Test switching back to no throttling + yield selectNetworkThrottling(ui, "No throttling"); + testNetworkThrottlingSelectorLabel(ui, "No throttling"); + yield testNetworkThrottlingState(ui, null); +}); + +function testNetworkThrottlingSelectorLabel(ui, expected) { + let selector = "#global-network-throttling-selector"; + let select = ui.toolWindow.document.querySelector(selector); + is(select.selectedOptions[0].textContent, expected, + `Select label should be changed to ${expected}`); +} + +var testNetworkThrottlingState = Task.async(function* (ui, expected) { + let state = yield ui.emulationFront.getNetworkThrottling(); + Assert.deepEqual(state, expected, "Network throttling state should be " + + JSON.stringify(expected, null, 2)); +}); + +var testThrottlingProfile = Task.async(function* (ui, profile) { + yield selectNetworkThrottling(ui, profile); + testNetworkThrottlingSelectorLabel(ui, profile); + let data = throttlingProfiles.find(({ id }) => id == profile); + let { download, upload, latency } = data; + yield testNetworkThrottlingState(ui, { + downloadThroughput: download, + uploadThroughput: upload, + latency, + }); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_page_state.js b/devtools/client/responsive.html/test/browser/browser_page_state.js new file mode 100644 index 000000000..306900535 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_page_state.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test page state to ensure page is not reloaded and session history is not +// modified. + +const DUMMY_1_URL = "http://example.com/"; +const TEST_URL = `${URL_ROOT}doc_page_state.html`; +const DUMMY_2_URL = "http://example.com/browser/"; + +add_task(function* () { + // Load up a sequence of pages: + // 0. DUMMY_1_URL + // 1. TEST_URL + // 2. DUMMY_2_URL + let tab = yield addTab(DUMMY_1_URL); + let browser = tab.linkedBrowser; + yield load(browser, TEST_URL); + yield load(browser, DUMMY_2_URL); + + // Check session history state + let history = yield getSessionHistory(browser); + is(history.index, 2, "At page 2 in history"); + is(history.entries.length, 3, "3 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, TEST_URL, "Page 1 URL matches"); + is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches"); + + // Go back one so we're at the test page + yield back(browser); + + // Check session history state + history = yield getSessionHistory(browser); + is(history.index, 1, "At page 1 in history"); + is(history.entries.length, 3, "3 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, TEST_URL, "Page 1 URL matches"); + is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches"); + + // Click on content to set an altered state that would be lost on reload + yield BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser); + + let { ui } = yield openRDM(tab); + + // Check color inside the viewport + let color = yield spawnViewportTask(ui, {}, function* () { + // eslint-disable-next-line mozilla/no-cpows-in-tests + return content.getComputedStyle(content.document.body) + .getPropertyValue("background-color"); + }); + is(color, "rgb(0, 128, 0)", + "Content is still modified from click in viewport"); + + yield closeRDM(tab); + + // Check color back in the browser tab + color = yield ContentTask.spawn(browser, {}, function* () { + // eslint-disable-next-line mozilla/no-cpows-in-tests + return content.getComputedStyle(content.document.body) + .getPropertyValue("background-color"); + }); + is(color, "rgb(0, 128, 0)", + "Content is still modified from click in browser tab"); + + // Check session history state + history = yield getSessionHistory(browser); + is(history.index, 1, "At page 1 in history"); + is(history.entries.length, 3, "3 pages in history"); + is(history.entries[0].uri, DUMMY_1_URL, "Page 0 URL matches"); + is(history.entries[1].uri, TEST_URL, "Page 1 URL matches"); + is(history.entries[2].uri, DUMMY_2_URL, "Page 2 URL matches"); + + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_permission_doorhanger.js b/devtools/client/responsive.html/test/browser/browser_permission_doorhanger.js new file mode 100644 index 000000000..68b594509 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_permission_doorhanger.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that permission popups asking for user approval still appear in RDM +const DUMMY_URL = "http://example.com/"; +const TEST_URL = `${URL_ROOT}geolocation.html`; + +function waitForGeolocationPrompt(win, browser) { + return new Promise(resolve => { + win.PopupNotifications.panel.addEventListener("popupshown", function popupShown() { + let notification = win.PopupNotifications.getNotification("geolocation", browser); + if (notification) { + win.PopupNotifications.panel.removeEventListener("popupshown", popupShown); + resolve(); + } + }); + }); +} + +add_task(function* () { + let tab = yield addTab(DUMMY_URL); + let browser = tab.linkedBrowser; + let win = browser.ownerGlobal; + + let waitPromptPromise = waitForGeolocationPrompt(win, browser); + + // Checks if a geolocation permission doorhanger appears when openning a page + // requesting geolocation + yield load(browser, TEST_URL); + yield waitPromptPromise; + + ok(true, "Permission doorhanger appeared without RDM enabled"); + + // Lets switch back to the dummy website and enable RDM + yield load(browser, DUMMY_URL); + let { ui } = yield openRDM(tab); + let newBrowser = ui.getViewportBrowser(); + + waitPromptPromise = waitForGeolocationPrompt(win, newBrowser); + + // Checks if the doorhanger appeared again when reloading the geolocation + // page inside RDM + yield load(browser, TEST_URL); + yield waitPromptPromise; + + ok(true, "Permission doorhanger appeared inside RDM"); + + yield closeRDM(tab); + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_resize_cmd.js b/devtools/client/responsive.html/test/browser/browser_resize_cmd.js new file mode 100644 index 000000000..7e96e866c --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_resize_cmd.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* global ResponsiveUIManager */ +/* eslint key-spacing: 0 */ + +add_task(function* () { + let manager = ResponsiveUIManager; + let done; + + function isOpen() { + return ResponsiveUIManager.isActiveForTab(gBrowser.selectedTab); + } + + const TEST_URL = "data:text/html;charset=utf-8,hi"; + yield helpers.addTabWithToolbar(TEST_URL, (options) => { + return helpers.audit(options, [ + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize toggle"); + }, + check: { + input: "resize toggle", + hints: "", + markup: "VVVVVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize toggle"); + }, + check: { + input: "resize toggle", + hints: "", + markup: "VVVVVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + ]); + }); + yield helpers.addTabWithToolbar(TEST_URL, (options) => { + return helpers.audit(options, [ + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize on"); + }, + check: { + input: "resize on", + hints: "", + markup: "VVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize off"); + }, + check: { + input: "resize off", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + ]); + }); + yield helpers.addTabWithToolbar(TEST_URL, (options) => { + return helpers.audit(options, [ + { + setup() { + done = once(manager, "on"); + return helpers.setInput(options, "resize to 400 400"); + }, + check: { + input: "resize to 400 400", + hints: "", + markup: "VVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + width: { value: 400 }, + height: { value: 400 }, + } + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(isOpen(), "responsive mode is open"); + }), + }, + { + setup() { + done = once(manager, "off"); + return helpers.setInput(options, "resize off"); + }, + check: { + input: "resize off", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "" + }, + post: Task.async(function* () { + yield done; + ok(!isOpen(), "responsive mode is closed"); + }), + }, + ]); + }); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_screenshot_button.js b/devtools/client/responsive.html/test/browser/browser_screenshot_button.js new file mode 100644 index 000000000..60605c33b --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_screenshot_button.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test global exit button + +const TEST_URL = "data:text/html;charset=utf-8,"; + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +function* waitUntilScreenshot() { + return new Promise(Task.async(function* (resolve) { + let { Downloads } = require("resource://gre/modules/Downloads.jsm"); + let list = yield Downloads.getList(Downloads.ALL); + + let view = { + onDownloadAdded: download => { + download.whenSucceeded().then(() => { + resolve(download.target.path); + list.removeView(view); + }); + } + }; + + yield list.addView(view); + })); +} + +addRDMTask(TEST_URL, function* ({ ui: {toolWindow} }) { + let { store, document } = toolWindow; + + // Wait until the viewport has been added + yield waitUntilState(store, state => state.viewports.length == 1); + + info("Click the screenshot button"); + let screenshotButton = document.getElementById("global-screenshot-button"); + screenshotButton.click(); + + let whenScreenshotSucceeded = waitUntilScreenshot(); + + let filePath = yield whenScreenshotSucceeded; + let image = new Image(); + image.src = OS.Path.toFileURI(filePath); + + yield once(image, "load"); + + // We have only one viewport at the moment + let viewport = store.getState().viewports[0]; + let ratio = window.devicePixelRatio; + + is(image.width, viewport.width * ratio, + "screenshot width has the expected width"); + + is(image.height, viewport.height * ratio, + "screenshot width has the expected height"); + + yield OS.File.remove(filePath); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_tab_close.js b/devtools/client/responsive.html/test/browser/browser_tab_close.js new file mode 100644 index 000000000..1c5ed7c91 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_tab_close.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Verify RDM closes synchronously when tabs are closed. + +const TEST_URL = "http://example.com/"; + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + let clientClosed = waitForClientClose(ui); + + closeRDM(tab, { + reason: "TabClose", + }); + + // This flag is set at the end of `ResponsiveUI.destroy`. If it is true + // without yielding on `closeRDM` above, then we must have closed + // synchronously. + is(ui.destroyed, true, "RDM closed synchronously"); + + yield clientClosed; + yield removeTab(tab); +}); + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + let clientClosed = waitForClientClose(ui); + + yield removeTab(tab); + + // This flag is set at the end of `ResponsiveUI.destroy`. If it is true without + // yielding on `closeRDM` itself and only removing the tab, then we must have closed + // synchronously in response to tab closing. + is(ui.destroyed, true, "RDM closed synchronously"); + + yield clientClosed; +}); diff --git a/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js b/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js new file mode 100644 index 000000000..7ce32ff28 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Verify RDM closes synchronously when tabs change remoteness. + +const TEST_URL = "http://example.com/"; + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + let clientClosed = waitForClientClose(ui); + + closeRDM(tab, { + reason: "BeforeTabRemotenessChange", + }); + + // This flag is set at the end of `ResponsiveUI.destroy`. If it is true + // without yielding on `closeRDM` above, then we must have closed + // synchronously. + is(ui.destroyed, true, "RDM closed synchronously"); + + yield clientClosed; + yield removeTab(tab); +}); + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let { ui } = yield openRDM(tab); + let clientClosed = waitForClientClose(ui); + + // Load URL that requires the main process, forcing a remoteness flip + yield load(tab.linkedBrowser, "about:robots"); + + // This flag is set at the end of `ResponsiveUI.destroy`. If it is true without + // yielding on `closeRDM` itself and only removing the tab, then we must have closed + // synchronously in response to tab closing. + is(ui.destroyed, true, "RDM closed synchronously"); + + yield clientClosed; + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_toolbox_computed_view.js b/devtools/client/responsive.html/test/browser/browser_toolbox_computed_view.js new file mode 100644 index 000000000..b0b51aa42 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_toolbox_computed_view.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the computed-view refreshes. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +addRDMTask(TEST_URI, function* ({ ui, manager }) { + info("Open the responsive design mode and set its size to 500x500 to start"); + yield setViewportSize(ui, manager, 500, 500); + + info("Open the inspector, computed-view and select the test node"); + let { inspector, view } = yield openComputedView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, inspector, ui, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, inspector, ui, manager); + + yield closeToolbox(); +}); + +function* testShrink(computedView, inspector, ui, manager) { + is(computedWidth(computedView), "500px", "Should show 500px initially."); + + let onRefresh = inspector.once("computed-view-refreshed"); + yield setViewportSize(ui, manager, 100, 100); + yield onRefresh; + + is(computedWidth(computedView), "100px", "Should be 100px after shrinking."); +} + +function* testGrow(computedView, inspector, ui, manager) { + let onRefresh = inspector.once("computed-view-refreshed"); + yield setViewportSize(ui, manager, 500, 500); + yield onRefresh; + + is(computedWidth(computedView), "500px", "Should be 500px after growing."); +} + +function computedWidth(computedView) { + for (let prop of computedView.propertyViews) { + if (prop.name === "width") { + return prop.valueNode.textContent; + } + } + return null; +} diff --git a/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js b/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js new file mode 100644 index 000000000..7cf012c44 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_toolbox_rule_view.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that when the viewport is resized, the rule-view refreshes. + +const TEST_URI = "data:text/html;charset=utf-8,<html><style>" + + "div {" + + " width: 500px;" + + " height: 10px;" + + " background: purple;" + + "} " + + "@media screen and (max-width: 200px) {" + + " div { " + + " width: 100px;" + + " }" + + "};" + + "</style><div></div></html>"; + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); +}); + +addRDMTask(TEST_URI, function* ({ ui, manager }) { + info("Open the responsive design mode and set its size to 500x500 to start"); + yield setViewportSize(ui, manager, 500, 500); + + info("Open the inspector, rule-view and select the test node"); + let { inspector, view } = yield openRuleView(); + yield selectNode("div", inspector); + + info("Try shrinking the viewport and checking the applied styles"); + yield testShrink(view, ui, manager); + + info("Try growing the viewport and checking the applied styles"); + yield testGrow(view, ui, manager); + + info("Check that ESC still opens the split console"); + yield testEscapeOpensSplitConsole(inspector); + + yield closeToolbox(); +}); + +function* testShrink(ruleView, ui, manager) { + is(numberOfRules(ruleView), 2, "Should have two rules initially."); + + info("Resize to 100x100 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setViewportSize(ui, manager, 100, 100); + yield onRefresh; + + is(numberOfRules(ruleView), 3, "Should have three rules after shrinking."); +} + +function* testGrow(ruleView, ui, manager) { + info("Resize to 500x500 and wait for the rule-view to update"); + let onRefresh = ruleView.once("ruleview-refreshed"); + yield setViewportSize(ui, manager, 500, 500); + yield onRefresh; + + is(numberOfRules(ruleView), 2, "Should have two rules after growing."); +} + +function* testEscapeOpensSplitConsole(inspector) { + ok(!inspector._toolbox._splitConsole, "Console is not split."); + + info("Press escape"); + let onSplit = inspector._toolbox.once("split-console"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onSplit; + + ok(inspector._toolbox._splitConsole, "Console is split after pressing ESC."); +} + +function numberOfRules(ruleView) { + return ruleView.element.querySelectorAll(".ruleview-code").length; +} diff --git a/devtools/client/responsive.html/test/browser/browser_toolbox_swap_browsers.js b/devtools/client/responsive.html/test/browser/browser_toolbox_swap_browsers.js new file mode 100644 index 000000000..8f7afaf01 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_toolbox_swap_browsers.js @@ -0,0 +1,128 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Verify that toolbox remains open when opening and closing RDM. + +const TEST_URL = "http://example.com/"; + +function getServerConnections(browser) { + ok(browser.isRemoteBrowser, "Content browser is remote"); + return ContentTask.spawn(browser, {}, function* () { + const Cu = Components.utils; + const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); + const { DebuggerServer } = require("devtools/server/main"); + if (!DebuggerServer._connections) { + return 0; + } + return Object.getOwnPropertyNames(DebuggerServer._connections); + }); +} + +let checkServerConnectionCount = Task.async(function* (browser, expected, msg) { + let conns = yield getServerConnections(browser); + is(conns.length || 0, expected, "Server connection count: " + msg); +}); + +let checkToolbox = Task.async(function* (tab, location) { + let target = TargetFactory.forTab(tab); + ok(!!gDevTools.getToolbox(target), `Toolbox exists ${location}`); +}); + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}); + +add_task(function* () { + let tab = yield addTab(TEST_URL); + + let tabsInDifferentProcesses = E10S_MULTI_ENABLED && + (gBrowser.tabs[0].linkedBrowser.frameLoader.childID != + gBrowser.tabs[1].linkedBrowser.frameLoader.childID); + + info("Open toolbox outside RDM"); + { + // 0: No DevTools connections yet + yield checkServerConnectionCount(tab.linkedBrowser, 0, + "0: No DevTools connections yet"); + let { toolbox } = yield openInspector(); + if (tabsInDifferentProcesses) { + // 1: Two tabs open, but only one per content process + yield checkServerConnectionCount(tab.linkedBrowser, 1, + "1: Two tabs open, but only one per content process"); + } else { + // 2: One for each tab (starting tab plus the one we opened) + yield checkServerConnectionCount(tab.linkedBrowser, 2, + "2: One for each tab (starting tab plus the one we opened)"); + } + yield checkToolbox(tab, "outside RDM"); + let { ui } = yield openRDM(tab); + if (tabsInDifferentProcesses) { + // 2: RDM UI adds an extra connection, 1 + 1 = 2 + yield checkServerConnectionCount(ui.getViewportBrowser(), 2, + "2: RDM UI uses an extra connection"); + } else { + // 3: RDM UI adds an extra connection, 2 + 1 = 3 + yield checkServerConnectionCount(ui.getViewportBrowser(), 3, + "3: RDM UI uses an extra connection"); + } + yield checkToolbox(tab, "after opening RDM"); + yield closeRDM(tab); + if (tabsInDifferentProcesses) { + // 1: RDM UI closed, return to previous connection count + yield checkServerConnectionCount(tab.linkedBrowser, 1, + "1: RDM UI closed, return to previous connection count"); + } else { + // 2: RDM UI closed, return to previous connection count + yield checkServerConnectionCount(tab.linkedBrowser, 2, + "2: RDM UI closed, return to previous connection count"); + } + yield checkToolbox(tab, tab.linkedBrowser, "after closing RDM"); + yield toolbox.destroy(); + // 0: All DevTools usage closed + yield checkServerConnectionCount(tab.linkedBrowser, 0, + "0: All DevTools usage closed"); + } + + info("Open toolbox inside RDM"); + { + // 0: No DevTools connections yet + yield checkServerConnectionCount(tab.linkedBrowser, 0, + "0: No DevTools connections yet"); + let { ui } = yield openRDM(tab); + // 1: RDM UI uses an extra connection + yield checkServerConnectionCount(ui.getViewportBrowser(), 1, + "1: RDM UI uses an extra connection"); + let { toolbox } = yield openInspector(); + if (tabsInDifferentProcesses) { + // 2: Two tabs open, but only one per content process + yield checkServerConnectionCount(ui.getViewportBrowser(), 2, + "2: Two tabs open, but only one per content process"); + } else { + // 3: One for each tab (starting tab plus the one we opened) + yield checkServerConnectionCount(ui.getViewportBrowser(), 3, + "3: One for each tab (starting tab plus the one we opened)"); + } + yield checkToolbox(tab, ui.getViewportBrowser(), "inside RDM"); + yield closeRDM(tab); + if (tabsInDifferentProcesses) { + // 1: RDM UI closed, one less connection + yield checkServerConnectionCount(tab.linkedBrowser, 1, + "1: RDM UI closed, one less connection"); + } else { + // 2: RDM UI closed, one less connection + yield checkServerConnectionCount(tab.linkedBrowser, 2, + "2: RDM UI closed, one less connection"); + } + yield checkToolbox(tab, tab.linkedBrowser, "after closing RDM"); + yield toolbox.destroy(); + // 0: All DevTools usage closed + yield checkServerConnectionCount(tab.linkedBrowser, 0, + "0: All DevTools usage closed"); + } + + yield removeTab(tab); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_touch_device.js b/devtools/client/responsive.html/test/browser/browser_touch_device.js new file mode 100644 index 000000000..aea6de2c4 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_touch_device.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests changing viewport touch simulation +const TEST_URL = "data:text/html;charset=utf-8,touch simulation test"; +const Types = require("devtools/client/responsive.html/types"); + +const testDevice = { + "name": "Fake Phone RDM Test", + "width": 320, + "height": 470, + "pixelRatio": 5.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "custom", + "featured": true, +}; + +// Add the new device to the list +addDeviceForTest(testDevice); + +addRDMTask(TEST_URL, function* ({ ui, manager }) { + yield waitStartup(ui); + + yield testDefaults(ui); + yield testChangingDevice(ui); + yield testResizingViewport(ui, true, false); + yield testEnableTouchSimulation(ui); + yield testResizingViewport(ui, false, true); +}); + +function* waitStartup(ui) { + let { store } = ui.toolWindow; + + // Wait until the viewport has been added and the device list has been loaded + yield waitUntilState(store, state => state.viewports.length == 1 + && state.devices.listState == Types.deviceListState.LOADED); +} + +function* testDefaults(ui) { + info("Test Defaults"); + + yield testTouchEventsOverride(ui, false); + testViewportDeviceSelectLabel(ui, "no device selected"); +} + +function* testChangingDevice(ui) { + info("Test Changing Device"); + + yield selectDevice(ui, testDevice.name); + yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height); + yield testTouchEventsOverride(ui, true); + testViewportDeviceSelectLabel(ui, testDevice.name); +} + +function* testResizingViewport(ui, device, expected) { + info(`Test resizing the viewport, device ${device}, expected ${expected}`); + + let deviceRemoved = once(ui, "device-removed"); + yield testViewportResize(ui, ".viewport-vertical-resize-handle", + [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui); + if (device) { + yield deviceRemoved; + } + yield testTouchEventsOverride(ui, expected); + testViewportDeviceSelectLabel(ui, "no device selected"); +} + +function* testEnableTouchSimulation(ui) { + info("Test enabling touch simulation via button"); + + yield enableTouchSimulation(ui); + yield testTouchEventsOverride(ui, true); +} diff --git a/devtools/client/responsive.html/test/browser/browser_touch_simulation.js b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js new file mode 100644 index 000000000..12a718306 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js @@ -0,0 +1,228 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test global touch simulation button + +const TEST_URL = `${URL_ROOT}touch.html`; +const PREF_DOM_META_VIEWPORT_ENABLED = "dom.meta-viewport.enabled"; + +addRDMTask(TEST_URL, function* ({ ui }) { + yield waitBootstrap(ui); + yield testWithNoTouch(ui); + yield enableTouchSimulation(ui); + yield testWithTouch(ui); + yield testWithMetaViewportEnabled(ui); + yield testWithMetaViewportDisabled(ui); + testTouchButton(ui); +}); + +function* testWithNoTouch(ui) { + yield injectEventUtils(ui); + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + let { EventUtils } = content; + + let div = content.document.querySelector("div"); + let x = 0, y = 0; + + info("testWithNoTouch: Initial test parameter and mouse mouse outside div"); + x = -1; y = -1; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + div.style.transform = "none"; + div.style.backgroundColor = ""; + + info("testWithNoTouch: Move mouse into the div element"); + yield EventUtils.synthesizeMouseAtCenter(div, + { type: "mousemove", isSynthesized: false }, content); + is(div.style.backgroundColor, "red", "mouseenter or mouseover should work"); + + info("testWithNoTouch: Drag the div element"); + yield EventUtils.synthesizeMouseAtCenter(div, + { type: "mousedown", isSynthesized: false }, content); + x = 100; y = 100; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + is(div.style.transform, "none", "touchmove shouldn't work"); + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mouseup", isSynthesized: false }, content); + + info("testWithNoTouch: Move mouse out of the div element"); + x = -1; y = -1; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + is(div.style.backgroundColor, "blue", "mouseout or mouseleave should work"); + + info("testWithNoTouch: Click the div element"); + yield EventUtils.synthesizeClick(div); + is(div.dataset.isDelay, "false", + "300ms delay between touch events and mouse events should not work"); + }); +} + +function* testWithTouch(ui) { + yield injectEventUtils(ui); + + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + let { EventUtils } = content; + + let div = content.document.querySelector("div"); + let x = 0, y = 0; + + info("testWithTouch: Initial test parameter and mouse mouse outside div"); + x = -1; y = -1; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + div.style.transform = "none"; + div.style.backgroundColor = ""; + + info("testWithTouch: Move mouse into the div element"); + yield EventUtils.synthesizeMouseAtCenter(div, + { type: "mousemove", isSynthesized: false }, content); + isnot(div.style.backgroundColor, "red", + "mouseenter or mouseover should not work"); + + info("testWithTouch: Drag the div element"); + yield EventUtils.synthesizeMouseAtCenter(div, + { type: "mousedown", isSynthesized: false }, content); + x = 100; y = 100; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + isnot(div.style.transform, "none", "touchmove should work"); + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mouseup", isSynthesized: false }, content); + + info("testWithTouch: Move mouse out of the div element"); + x = -1; y = -1; + yield EventUtils.synthesizeMouse(div, x, y, + { type: "mousemove", isSynthesized: false }, content); + isnot(div.style.backgroundColor, "blue", + "mouseout or mouseleave should not work"); + }); +} + +function* testWithMetaViewportEnabled(ui) { + yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, true]]}); + + yield injectEventUtils(ui); + + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + let { synthesizeClick } = content.EventUtils; + + let meta = content.document.querySelector("meta[name=viewport]"); + let div = content.document.querySelector("div"); + div.dataset.isDelay = "false"; + + info("testWithMetaViewportEnabled: " + + "click the div element with <meta name='viewport'>"); + meta.content = ""; + yield synthesizeClick(div); + is(div.dataset.isDelay, "true", + "300ms delay between touch events and mouse events should work"); + + info("testWithMetaViewportEnabled: " + + "click the div element with " + + "<meta name='viewport' content='user-scalable=no'>"); + meta.content = "user-scalable=no"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", + "300ms delay between touch events and mouse events should not work"); + + info("testWithMetaViewportEnabled: " + + "click the div element with " + + "<meta name='viewport' content='minimum-scale=maximum-scale'>"); + meta.content = "minimum-scale=maximum-scale"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", + "300ms delay between touch events and mouse events should not work"); + + info("testWithMetaViewportEnabled: " + + "click the div element with " + + "<meta name='viewport' content='width=device-width'>"); + meta.content = "width=device-width"; + yield synthesizeClick(div); + is(div.dataset.isDelay, "false", + "300ms delay between touch events and mouse events should not work"); + }); +} + +function* testWithMetaViewportDisabled(ui) { + yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, false]]}); + + yield injectEventUtils(ui); + + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + let { synthesizeClick } = content.EventUtils; + + let meta = content.document.querySelector("meta[name=viewport]"); + let div = content.document.querySelector("div"); + div.dataset.isDelay = "false"; + + info("testWithMetaViewportDisabled: click the div with <meta name='viewport'>"); + meta.content = ""; + yield synthesizeClick(div); + is(div.dataset.isDelay, "true", + "300ms delay between touch events and mouse events should work"); + }); +} + +function testTouchButton(ui) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + + ok(touchButton.classList.contains("active"), + "Touch simulation is active at end of test."); + + touchButton.click(); + + ok(!touchButton.classList.contains("active"), + "Touch simulation is stopped on click."); + + touchButton.click(); + + ok(touchButton.classList.contains("active"), + "Touch simulation is started on click."); +} + +function* waitBootstrap(ui) { + let { store } = ui.toolWindow; + + yield waitUntilState(store, state => state.viewports.length == 1); + yield waitForFrameLoad(ui, TEST_URL); +} + +function* injectEventUtils(ui) { + yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () { + if ("EventUtils" in content) { + return; + } + + let EventUtils = content.EventUtils = {}; + + EventUtils.window = {}; + EventUtils.parent = EventUtils.window; + /* eslint-disable camelcase */ + EventUtils._EU_Ci = Components.interfaces; + EventUtils._EU_Cc = Components.classes; + /* eslint-enable camelcase */ + // EventUtils' `sendChar` function relies on the navigator to synthetize events. + EventUtils.navigator = content.navigator; + EventUtils.KeyboardEvent = content.KeyboardEvent; + + EventUtils.synthesizeClick = element => new Promise(resolve => { + element.addEventListener("click", function onClick() { + element.removeEventListener("click", onClick); + resolve(); + }); + + EventUtils.synthesizeMouseAtCenter(element, + { type: "mousedown", isSynthesized: false }, content); + EventUtils.synthesizeMouseAtCenter(element, + { type: "mouseup", isSynthesized: false }, content); + }); + + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + }); +} diff --git a/devtools/client/responsive.html/test/browser/browser_viewport_basics.js b/devtools/client/responsive.html/test/browser/browser_viewport_basics.js new file mode 100644 index 000000000..86fc41da9 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_viewport_basics.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test viewports basics after opening, like size and location + +const TEST_URL = "http://example.org/"; + +addRDMTask(TEST_URL, function* ({ ui }) { + let store = ui.toolWindow.store; + + // Wait until the viewport has been added + yield waitUntilState(store, state => state.viewports.length == 1); + + // A single viewport of default size appeared + let viewport = ui.toolWindow.document.querySelector(".viewport-content"); + + is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"), + "320px", "Viewport has default width"); + is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"), + "480px", "Viewport has default height"); + + // Browser's location should match original tab + yield waitForFrameLoad(ui, TEST_URL); + let location = yield spawnViewportTask(ui, {}, function* () { + return content.location.href; // eslint-disable-line + }); + is(location, TEST_URL, "Viewport location matches"); +}); diff --git a/devtools/client/responsive.html/test/browser/browser_window_close.js b/devtools/client/responsive.html/test/browser/browser_window_close.js new file mode 100644 index 000000000..29d9d1e34 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/browser_window_close.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* () { + let newWindowPromise = BrowserTestUtils.waitForNewWindow(); + window.open("data:text/html;charset=utf-8,", "_blank"); + let newWindow = yield newWindowPromise; + + newWindow.focus(); + yield once(newWindow.gBrowser, "load", true); + + let tab = newWindow.gBrowser.selectedTab; + yield openRDM(tab); + + // Close the window on a tab with an active responsive design UI and + // wait for the UI to gracefully shutdown. This has leaked the window + // in the past. + ok(ResponsiveUIManager.isActiveForTab(tab), + "ResponsiveUI should be active for tab when the window is closed"); + let offPromise = once(ResponsiveUIManager, "off"); + yield BrowserTestUtils.closeWindow(newWindow); + yield offPromise; +}); diff --git a/devtools/client/responsive.html/test/browser/devices.json b/devtools/client/responsive.html/test/browser/devices.json new file mode 100644 index 000000000..c3f2bb363 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/devices.json @@ -0,0 +1,651 @@ +{ + "TYPES": [ "phones", "tablets", "laptops", "televisions", "consoles", "watches" ], + "phones": [ + { + "name": "Firefox OS Flame", + "width": 320, + "height": 570, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Alcatel One Touch Fire", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch4012X; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Alcatel One Touch Fire C", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch4019X; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Alcatel One Touch Fire E", + "width": 320, + "height": 480, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Mobile; ALCATELOneTouch6015X; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Apple iPhone 4", + "width": 320, + "height": 480, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "Apple iPhone 5", + "width": 320, + "height": 568, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "Apple iPhone 5s", + "width": 320, + "height": 568, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13D15 Safari/601.1", + "touch": true, + "firefoxOS": false, + "os": "ios", + "featured": true + }, + { + "name": "Apple iPhone 6", + "width": 375, + "height": 667, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "Apple iPhone 6 Plus", + "width": 414, + "height": 736, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", + "touch": true, + "firefoxOS": false, + "os": "ios", + "featured": true + }, + { + "name": "Apple iPhone 6s", + "width": 375, + "height": 667, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", + "touch": true, + "firefoxOS": false, + "os": "ios", + "featured": true + }, + { + "name": "Apple iPhone 6s Plus", + "width": 414, + "height": 736, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "BlackBerry Z30", + "width": 360, + "height": 640, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+", + "touch": true, + "firefoxOS": false, + "os": "blackberryos" + }, + { + "name": "Geeksphone Keon", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Geeksphone Peak, Revolution", + "width": 360, + "height": 640, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Google Nexus S", + "width": 320, + "height": 533, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Nexus S Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Google Nexus 4", + "width": 384, + "height": 640, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.4; en-us; Nexus 4 Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36", + "touch": true, + "firefoxOS": true, + "os": "android", + "featured": true + }, + { + "name": "Google Nexus 5", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.4; en-us; Nexus 5 Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36", + "touch": true, + "firefoxOS": true, + "os": "android", + "featured": true + }, + { + "name": "Google Nexus 6", + "width": 412, + "height": 732, + "pixelRatio": 3.5, + "userAgent": "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Mobile Safari/537.36", + "touch": true, + "firefoxOS": true, + "os": "android", + "featured": true + }, + { + "name": "Intex Cloud Fx", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "KDDI Fx0", + "width": 360, + "height": 640, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Mobile; LGL25; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "LG Fireweb", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; LG-D300; rv:18.1) Gecko/18.1 Firefox/18.1", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "LG Optimus L70", + "width": 384, + "height": 640, + "pixelRatio": 1.25, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Nokia Lumia 520", + "width": 320, + "height": 533, + "pixelRatio": 1.4, + "userAgent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)", + "touch": true, + "firefoxOS": false, + "os": "android", + "featured": true + }, + { + "name": "Nokia N9", + "width": 360, + "height": 640, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "OnePlus One", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Android 5.1.1; Mobile; rv:43.0) Gecko/43.0 Firefox/43.0", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Samsung Galaxy S3", + "width": 360, + "height": 640, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Samsung Galaxy S4", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Samsung Galaxy S5", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android", + "featured": true + }, + { + "name": "Samsung Galaxy S6", + "width": 360, + "height": 640, + "pixelRatio": 4, + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android", + "featured": true + }, + { + "name": "Sony Xperia Z3", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Spice Fire One Mi-FX1", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Symphony GoFox F15", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; rv:30.0) Gecko/30.0 Firefox/30.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "ZTE Open", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; ZTEOPEN; rv:18.1) Gecko/18.0 Firefox/18.1", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "ZTE Open II", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; OPEN2; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "ZTE Open C", + "width": 320, + "height": 450, + "pixelRatio": 1.5, + "userAgent": "Mozilla/5.0 (Mobile; OPENC; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + }, + { + "name": "Zen Fire 105", + "width": 320, + "height": 480, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Mobile; rv:28.0) Gecko/28.0 Firefox/28.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + } + ], + "tablets": [ + { + "name": "Amazon Kindle Fire HDX 8.9", + "width": 1280, + "height": 800, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true", + "touch": true, + "firefoxOS": false, + "os": "fireos", + "featured": true + }, + { + "name": "Apple iPad", + "width": 1024, + "height": 768, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "Apple iPad Air 2", + "width": 1024, + "height": 768, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5", + "touch": true, + "firefoxOS": false, + "os": "ios", + "featured": true + }, + { + "name": "Apple iPad Mini", + "width": 1024, + "height": 768, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5", + "touch": true, + "firefoxOS": false, + "os": "ios" + }, + { + "name": "Apple iPad Mini 2", + "width": 1024, + "height": 768, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5", + "touch": true, + "firefoxOS": false, + "os": "ios", + "featured": true + }, + { + "name": "BlackBerry PlayBook", + "width": 1024, + "height": 600, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+", + "touch": true, + "firefoxOS": false, + "os": "blackberryos" + }, + { + "name": "Foxconn InFocus", + "width": 1280, + "height": 800, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Tablet; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Google Nexus 7", + "width": 960, + "height": 600, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android", + "featured": true + }, + { + "name": "Google Nexus 10", + "width": 1280, + "height": 800, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Mobile Safari/537.36", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Samsung Galaxy Note 2", + "width": 360, + "height": 640, + "pixelRatio": 2, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "touch": true, + "firefoxOS": false, + "os": "android" + }, + { + "name": "Samsung Galaxy Note 3", + "width": 360, + "height": 640, + "pixelRatio": 3, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "touch": true, + "firefoxOS": false, + "os": "android", + "featured": true + }, + { + "name": "Tesla Model S", + "width": 1200, + "height": 1920, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (X11; Linux) AppleWebKit/534.34 (KHTML, like Gecko) QtCarBrowser Safari/534.34", + "touch": true, + "firefoxOS": false, + "os": "linux" + }, + { + "name": "VIA Vixen", + "width": 1024, + "height": 600, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Tablet; rv:32.0) Gecko/32.0 Firefox/32.0", + "touch": true, + "firefoxOS": true, + "os": "fxos" + } + ], + "laptops": [ + { + "name": "Laptop (1366 x 768)", + "width": 1366, + "height": 768, + "pixelRatio": 1, + "userAgent": "", + "touch": false, + "firefoxOS": false, + "os": "windows", + "featured": true + }, + { + "name": "Laptop (1920 x 1080)", + "width": 1280, + "height": 720, + "pixelRatio": 1.5, + "userAgent": "", + "touch": false, + "firefoxOS": false, + "os": "windows", + "featured": true + }, + { + "name": "Laptop (1920 x 1080) with touch", + "width": 1280, + "height": 720, + "pixelRatio": 1.5, + "userAgent": "", + "touch": true, + "firefoxOS": false, + "os": "windows" + } + ], + "televisions": [ + { + "name": "720p HD Television", + "width": 1280, + "height": 720, + "pixelRatio": 1, + "userAgent": "", + "touch": false, + "firefoxOS": true, + "os": "custom" + }, + { + "name": "1080p Full HD Television", + "width": 1920, + "height": 1080, + "pixelRatio": 1, + "userAgent": "", + "touch": false, + "firefoxOS": true, + "os": "custom" + }, + { + "name": "4K Ultra HD Television", + "width": 3840, + "height": 2160, + "pixelRatio": 1, + "userAgent": "", + "touch": false, + "firefoxOS": true, + "os": "custom" + } + ], + "consoles": [ + { + "name": "Nintendo 3DS", + "width": 320, + "height": 240, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7585.EU", + "touch": true, + "firefoxOS": false, + "os": "nintendo" + }, + { + "name": "Nintendo Wii U Gamepad", + "width": 854, + "height": 480, + "pixelRatio": 0.87, + "userAgent": "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.28 (KHTML, like Gecko) NX/3.0.3.12.15 NintendoBrowser/4.1.1.9601.EU", + "touch": true, + "firefoxOS": false, + "os": "nintendo" + }, + { + "name": "Sony PlayStation Vita", + "width": 960, + "height": 544, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Playstation Vita 1.61) AppleWebKit/531.22.8 (KHTML, like Gecko) Silk/3.2", + "touch": true, + "firefoxOS": false, + "os": "playstation" + } + ], + "watches": [ + { + "name": "LG G Watch", + "width": 280, + "height": 280, + "pixelRatio": 1, + "userAgent": "", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "LG G Watch R", + "width": 320, + "height": 320, + "pixelRatio": 1, + "userAgent": "", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Motorola Moto 360", + "width": 320, + "height": 290, + "pixelRatio": 1, + "userAgent": "Mozilla/5.0 (Linux; Android 5.0.1; Moto 360 Build/LWX48T) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/19.77.34.5 Mobile Safari/537.36", + "touch": true, + "firefoxOS": true, + "os": "android" + }, + { + "name": "Samsung Gear Live", + "width": 320, + "height": 320, + "pixelRatio": 1, + "userAgent": "", + "touch": true, + "firefoxOS": true, + "os": "android" + } + ] +} diff --git a/devtools/client/responsive.html/test/browser/doc_page_state.html b/devtools/client/responsive.html/test/browser/doc_page_state.html new file mode 100644 index 000000000..fb4d2acf0 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/doc_page_state.html @@ -0,0 +1,16 @@ +<!doctype html> +<html> + <head> + <title>Page State Test</title> + <style> + body { + height: 100vh; + background: red; + } + body.modified { + background: green; + } + </style> + </head> + <body onclick="this.classList.add('modified')"/> +</html> diff --git a/devtools/client/responsive.html/test/browser/geolocation.html b/devtools/client/responsive.html/test/browser/geolocation.html new file mode 100644 index 000000000..03d105a19 --- /dev/null +++ b/devtools/client/responsive.html/test/browser/geolocation.html @@ -0,0 +1,13 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Geolocation permission test</title> + </head> + <body> + <script type="text/javascript"> + "use strict"; + navigator.geolocation.getCurrentPosition(function (pos) {}); + </script> + </body> +</html>
\ No newline at end of file diff --git a/devtools/client/responsive.html/test/browser/head.js b/devtools/client/responsive.html/test/browser/head.js new file mode 100644 index 000000000..3be69b0af --- /dev/null +++ b/devtools/client/responsive.html/test/browser/head.js @@ -0,0 +1,401 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../../framework/test/shared-head.js */ +/* import-globals-from ../../../framework/test/shared-redux-head.js */ +/* import-globals-from ../../../commandline/test/helpers.js */ +/* import-globals-from ../../../inspector/test/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", + this); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js", + this); + +// Import the GCLI test helper +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js", + this); + +// Import helpers registering the test-actor in remote targets +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js", + this); + +// Import helpers for the inspector that are also shared with others +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this); + +const E10S_MULTI_ENABLED = Services.prefs.getIntPref("dom.ipc.processCount") > 1; +const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/"; +const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL"; + +const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices"); +const { getOwnerWindow } = require("sdk/tabs/utils"); +const asyncStorage = require("devtools/shared/async-storage"); +const { addDevice, removeDevice } = require("devtools/client/shared/devices"); + +SimpleTest.requestCompleteLog(); +SimpleTest.waitForExplicitFinish(); + +// Toggling the RDM UI involves several docShell swap operations, which are somewhat slow +// on debug builds. Usually we are just barely over the limit, so a blanket factor of 2 +// should be enough. +requestLongerTimeout(2); + +flags.testing = true; +Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); +Services.prefs.setCharPref("devtools.devices.url", + TEST_URI_ROOT + "devices.json"); +Services.prefs.setBoolPref("devtools.responsive.html.enabled", true); + +registerCleanupFunction(() => { + flags.testing = false; + Services.prefs.clearUserPref("devtools.devices.url"); + Services.prefs.clearUserPref("devtools.responsive.html.enabled"); + Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList"); + asyncStorage.removeItem("devtools.devices.url_cache"); +}); + +// This depends on the "devtools.responsive.html.enabled" pref +const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm"); + +/** + * Open responsive design mode for the given tab. + */ +var openRDM = Task.async(function* (tab) { + info("Opening responsive design mode"); + let manager = ResponsiveUIManager; + let ui = yield manager.openIfNeeded(getOwnerWindow(tab), tab); + info("Responsive design mode opened"); + return { ui, manager }; +}); + +/** + * Close responsive design mode for the given tab. + */ +var closeRDM = Task.async(function* (tab, options) { + info("Closing responsive design mode"); + let manager = ResponsiveUIManager; + yield manager.closeIfNeeded(getOwnerWindow(tab), tab, options); + info("Responsive design mode closed"); +}); + +/** + * Adds a new test task that adds a tab with the given URL, opens responsive + * design mode, runs the given generator, closes responsive design mode, and + * removes the tab. + * + * Example usage: + * + * addRDMTask(TEST_URL, function*({ ui, manager }) { + * // Your tests go here... + * }); + */ +function addRDMTask(url, generator) { + add_task(function* () { + const tab = yield addTab(url); + const results = yield openRDM(tab); + + try { + yield* generator(results); + } catch (err) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(err)); + } + + yield closeRDM(tab); + yield removeTab(tab); + }); +} + +function spawnViewportTask(ui, args, task) { + return ContentTask.spawn(ui.getViewportBrowser(), args, task); +} + +function waitForFrameLoad(ui, targetURL) { + return spawnViewportTask(ui, { targetURL }, function* (args) { + if ((content.document.readyState == "complete" || + content.document.readyState == "interactive") && + content.location.href == args.targetURL) { + return; + } + yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded"); + }); +} + +function waitForViewportResizeTo(ui, width, height) { + return new Promise(Task.async(function* (resolve) { + let isSizeMatching = (data) => data.width == width && data.height == height; + + // If the viewport has already the expected size, we resolve the promise immediately. + let size = yield getContentSize(ui); + if (isSizeMatching(size)) { + resolve(); + return; + } + + // Otherwise, we'll listen to both content's resize event and browser's load end; + // since a racing condition can happen, where the content's listener is added after + // the resize, because the content's document was reloaded; therefore the test would + // hang forever. See bug 1302879. + let browser = ui.getViewportBrowser(); + + let onResize = (_, data) => { + if (!isSizeMatching(data)) { + return; + } + ui.off("content-resize", onResize); + browser.removeEventListener("mozbrowserloadend", onBrowserLoadEnd); + info(`Got content-resize to ${width} x ${height}`); + resolve(); + }; + + let onBrowserLoadEnd = Task.async(function* () { + let data = yield getContentSize(ui); + onResize(undefined, data); + }); + + info(`Waiting for content-resize to ${width} x ${height}`); + ui.on("content-resize", onResize); + browser.addEventListener("mozbrowserloadend", + onBrowserLoadEnd, { once: true }); + })); +} + +var setViewportSize = Task.async(function* (ui, manager, width, height) { + let size = ui.getViewportSize(); + info(`Current size: ${size.width} x ${size.height}, ` + + `set to: ${width} x ${height}`); + if (size.width != width || size.height != height) { + let resized = waitForViewportResizeTo(ui, width, height); + ui.setViewportSize({ width, height }); + yield resized; + } +}); + +function getElRect(selector, win) { + let el = win.document.querySelector(selector); + return el.getBoundingClientRect(); +} + +/** + * Drag an element identified by 'selector' by [x,y] amount. Returns + * the rect of the dragged element as it was before drag. + */ +function dragElementBy(selector, x, y, win) { + let React = win.require("devtools/client/shared/vendor/react"); + let { Simulate } = React.addons.TestUtils; + let rect = getElRect(selector, win); + let startPoint = { + clientX: rect.left + Math.floor(rect.width / 2), + clientY: rect.top + Math.floor(rect.height / 2), + }; + let endPoint = [ startPoint.clientX + x, startPoint.clientY + y ]; + + let elem = win.document.querySelector(selector); + + // mousedown is a React listener, need to use its testing tools to avoid races + Simulate.mouseDown(elem, startPoint); + + // mousemove and mouseup are regular DOM listeners + EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mousemove" }, win); + EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mouseup" }, win); + + return rect; +} + +function* testViewportResize(ui, selector, moveBy, + expectedViewportSize, expectedHandleMove) { + let win = ui.toolWindow; + let resized = waitForViewportResizeTo(ui, ...expectedViewportSize); + let startRect = dragElementBy(selector, ...moveBy, win); + yield resized; + + let endRect = getElRect(selector, win); + is(endRect.left - startRect.left, expectedHandleMove[0], + `The x move of ${selector} is as expected`); + is(endRect.top - startRect.top, expectedHandleMove[1], + `The y move of ${selector} is as expected`); +} + +function openDeviceModal({ toolWindow }) { + let { document } = toolWindow; + let React = toolWindow.require("devtools/client/shared/vendor/react"); + let { Simulate } = React.addons.TestUtils; + let select = document.querySelector(".viewport-device-selector"); + let modal = document.querySelector("#device-modal-wrapper"); + + info("Checking initial device modal state"); + ok(modal.classList.contains("closed") && !modal.classList.contains("opened"), + "The device modal is closed by default."); + + info("Opening device modal through device selector."); + select.value = OPEN_DEVICE_MODAL_VALUE; + Simulate.change(select); + ok(modal.classList.contains("opened") && !modal.classList.contains("closed"), + "The device modal is displayed."); +} + +function changeSelectValue({ toolWindow }, selector, value) { + info(`Selecting ${value} in ${selector}.`); + + return new Promise(resolve => { + let select = toolWindow.document.querySelector(selector); + isnot(select, null, `selector "${selector}" should match an existing element.`); + + let option = [...select.options].find(o => o.value === String(value)); + isnot(option, undefined, `value "${value}" should match an existing option.`); + + let event = new toolWindow.UIEvent("change", { + view: toolWindow, + bubbles: true, + cancelable: true + }); + + select.addEventListener("change", () => { + is(select.value, value, + `Select's option with value "${value}" should be selected.`); + resolve(); + }, { once: true }); + + select.value = value; + select.dispatchEvent(event); + }); +} + +const selectDevice = (ui, value) => Promise.all([ + once(ui, "device-changed"), + changeSelectValue(ui, ".viewport-device-selector", value) +]); + +const selectDPR = (ui, value) => + changeSelectValue(ui, "#global-dpr-selector > select", value); + +const selectNetworkThrottling = (ui, value) => Promise.all([ + once(ui, "network-throttling-changed"), + changeSelectValue(ui, "#global-network-throttling-selector", value) +]); + +function getSessionHistory(browser) { + return ContentTask.spawn(browser, {}, function* () { + /* eslint-disable no-undef */ + let { interfaces: Ci } = Components; + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let sessionHistory = webNav.sessionHistory; + let result = { + index: sessionHistory.index, + entries: [] + }; + + for (let i = 0; i < sessionHistory.count; i++) { + let entry = sessionHistory.getEntryAtIndex(i, false); + result.entries.push({ + uri: entry.URI.spec, + title: entry.title + }); + } + + return result; + /* eslint-enable no-undef */ + }); +} + +function getContentSize(ui) { + return spawnViewportTask(ui, {}, () => ({ + width: content.screen.width, + height: content.screen.height + })); +} + +function waitForPageShow(browser) { + let mm = browser.messageManager; + return new Promise(resolve => { + let onShow = message => { + if (message.target != browser) { + return; + } + mm.removeMessageListener("PageVisibility:Show", onShow); + resolve(); + }; + mm.addMessageListener("PageVisibility:Show", onShow); + }); +} + +function waitForViewportLoad(ui) { + return new Promise(resolve => { + let browser = ui.getViewportBrowser(); + browser.addEventListener("mozbrowserloadend", () => { + resolve(); + }, { once: true }); + }); +} + +function load(browser, url) { + let loaded = BrowserTestUtils.browserLoaded(browser, false, url); + browser.loadURI(url, null, null); + return loaded; +} + +function back(browser) { + let shown = waitForPageShow(browser); + browser.goBack(); + return shown; +} + +function forward(browser) { + let shown = waitForPageShow(browser); + browser.goForward(); + return shown; +} + +function addDeviceForTest(device) { + info(`Adding Test Device "${device.name}" to the list.`); + addDevice(device); + + registerCleanupFunction(() => { + // Note that assertions in cleanup functions are not displayed unless they failed. + ok(removeDevice(device), `Removed Test Device "${device.name}" from the list.`); + }); +} + +function waitForClientClose(ui) { + return new Promise(resolve => { + info("Waiting for RDM debugger client to close"); + ui.client.addOneTimeListener("closed", () => { + info("RDM's debugger client is now closed"); + resolve(); + }); + }); +} + +function* testTouchEventsOverride(ui, expected) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + + let flag = yield ui.emulationFront.getTouchEventsOverride(); + is(flag === Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED, expected, + `Touch events override should be ${expected ? "enabled" : "disabled"}`); + is(touchButton.classList.contains("active"), expected, + `Touch simulation button should be ${expected ? "" : "in"}active.`); +} + +function testViewportDeviceSelectLabel(ui, expected) { + info("Test viewport's device select label"); + + let select = ui.toolWindow.document.querySelector(".viewport-device-selector"); + is(select.selectedOptions[0].textContent, expected, + `Device Select value should be: ${expected}`); +} + +function* enableTouchSimulation(ui) { + let { document } = ui.toolWindow; + let touchButton = document.querySelector("#global-touch-simulation-button"); + let loaded = waitForViewportLoad(ui); + touchButton.click(); + yield loaded; +} diff --git a/devtools/client/responsive.html/test/browser/touch.html b/devtools/client/responsive.html/test/browser/touch.html new file mode 100644 index 000000000..98aeac68f --- /dev/null +++ b/devtools/client/responsive.html/test/browser/touch.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> + +<meta charset="utf-8" /> +<meta name="viewport" /> +<title>test</title> + + +<style> + div { + border :1px solid red; + width: 100px; height: 100px; + } +</style> + +<div data-is-delay="false"></div> + +<script type="text/javascript;version=1.8"> + "use strict"; + let div = document.querySelector("div"); + let initX, initY; + let previousEvent = "", touchendTime = 0; + let updatePreviousEvent = function (e) { + previousEvent = e.type; + }; + + div.style.transform = "none"; + div.style.backgroundColor = ""; + + div.addEventListener("touchstart", function (evt) { + let touch = evt.changedTouches[0]; + initX = touch.pageX; + initY = touch.pageY; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("touchmove", function (evt) { + let touch = evt.changedTouches[0]; + let deltaX = touch.pageX - initX; + let deltaY = touch.pageY - initY; + div.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("touchend", function (evt) { + if (!evt.touches.length) { + div.style.transform = "none"; + } + touchendTime = performance.now(); + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseenter", function (evt) { + div.style.backgroundColor = "red"; + updatePreviousEvent(evt); + }, true); + div.addEventListener("mouseover", function(evt) { + div.style.backgroundColor = "red"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseout", function (evt) { + div.style.backgroundColor = "blue"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mouseleave", function (evt) { + div.style.backgroundColor = "blue"; + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mousedown", function (evt) { + if (previousEvent === "touchend" && touchendTime !== 0) { + let now = performance.now(); + div.dataset.isDelay = ((now - touchendTime) >= 300); + } else { + div.dataset.isDelay = false; + } + updatePreviousEvent(evt); + }, true); + + div.addEventListener("mousemove", updatePreviousEvent, true); + + div.addEventListener("mouseup", updatePreviousEvent, true); + + div.addEventListener("click", updatePreviousEvent, true); +</script> |