browser_contentAreaClick.js (9818B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Test for bug 549340. 7 * Test for browser.js::contentAreaClick() util. 8 * 9 * The test opens a new browser window, then replaces browser.js methods invoked 10 * by contentAreaClick with a mock function that tracks which methods have been 11 * called. 12 * Each sub-test synthesizes a mouse click event on links injected in content, 13 * the event is collected by a click handler that ensures that contentAreaClick 14 * correctly prevent default events, and follows the correct code path. 15 */ 16 17 const { sinon } = ChromeUtils.importESModule( 18 "resource://testing-common/Sinon.sys.mjs" 19 ); 20 21 var gTests = [ 22 { 23 desc: "Simple left click", 24 setup() {}, 25 clean() {}, 26 event: {}, 27 targets: ["commonlink", "mathlink", "svgxlink", "maplink"], 28 expectedInvokedMethods: [], 29 preventDefault: false, 30 }, 31 32 { 33 desc: "Ctrl/Cmd left click", 34 setup() {}, 35 clean() {}, 36 event: { ctrlKey: true, metaKey: true }, 37 targets: ["commonlink", "mathlink", "svgxlink", "maplink"], 38 expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], 39 preventDefault: true, 40 }, 41 42 // The next test should just be like Alt click. 43 { 44 desc: "Shift+Alt left click", 45 setup() { 46 Services.prefs.setBoolPref("browser.altClickSave", true); 47 }, 48 clean() { 49 Services.prefs.clearUserPref("browser.altClickSave"); 50 }, 51 event: { shiftKey: true, altKey: true }, 52 targets: ["commonlink", "maplink"], 53 expectedInvokedMethods: ["gatherTextUnder", "saveURL"], 54 preventDefault: true, 55 }, 56 57 { 58 desc: "Shift+Alt left click on XLinks", 59 setup() { 60 Services.prefs.setBoolPref("browser.altClickSave", true); 61 }, 62 clean() { 63 Services.prefs.clearUserPref("browser.altClickSave"); 64 }, 65 event: { shiftKey: true, altKey: true }, 66 targets: ["mathlink", "svgxlink"], 67 expectedInvokedMethods: ["saveURL"], 68 preventDefault: true, 69 }, 70 71 { 72 desc: "Shift click", 73 setup() {}, 74 clean() {}, 75 event: { shiftKey: true }, 76 targets: ["commonlink", "mathlink", "svgxlink", "maplink"], 77 expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], 78 preventDefault: true, 79 }, 80 81 { 82 desc: "Alt click", 83 setup() { 84 Services.prefs.setBoolPref("browser.altClickSave", true); 85 }, 86 clean() { 87 Services.prefs.clearUserPref("browser.altClickSave"); 88 }, 89 event: { altKey: true }, 90 targets: ["commonlink", "maplink"], 91 expectedInvokedMethods: ["gatherTextUnder", "saveURL"], 92 preventDefault: true, 93 }, 94 95 { 96 desc: "Alt click on XLinks", 97 setup() { 98 Services.prefs.setBoolPref("browser.altClickSave", true); 99 }, 100 clean() { 101 Services.prefs.clearUserPref("browser.altClickSave"); 102 }, 103 event: { altKey: true }, 104 targets: ["mathlink", "svgxlink"], 105 expectedInvokedMethods: ["saveURL"], 106 preventDefault: true, 107 }, 108 109 { 110 desc: "Panel click", 111 setup() {}, 112 clean() {}, 113 event: {}, 114 targets: ["panellink"], 115 expectedInvokedMethods: ["urlSecurityCheck", "loadURI"], 116 preventDefault: true, 117 }, 118 119 { 120 desc: "Simple middle click opentab", 121 setup() {}, 122 clean() {}, 123 event: { button: 1 }, 124 wantedEvent: "auxclick", 125 targets: ["commonlink", "mathlink", "svgxlink", "maplink"], 126 expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], 127 preventDefault: true, 128 }, 129 130 { 131 desc: "Simple middle click openwin", 132 setup() { 133 Services.prefs.setBoolPref("browser.tabs.opentabfor.middleclick", false); 134 }, 135 clean() { 136 Services.prefs.clearUserPref("browser.tabs.opentabfor.middleclick"); 137 }, 138 event: { button: 1 }, 139 wantedEvent: "auxclick", 140 targets: ["commonlink", "mathlink", "svgxlink", "maplink"], 141 expectedInvokedMethods: ["urlSecurityCheck", "openLinkIn"], 142 preventDefault: true, 143 }, 144 145 { 146 desc: "Middle mouse paste", 147 setup() { 148 Services.prefs.setBoolPref("middlemouse.contentLoadURL", true); 149 Services.prefs.setBoolPref("general.autoScroll", false); 150 }, 151 clean() { 152 Services.prefs.clearUserPref("middlemouse.contentLoadURL"); 153 Services.prefs.clearUserPref("general.autoScroll"); 154 }, 155 event: { button: 1 }, 156 wantedEvent: "auxclick", 157 targets: ["emptylink"], 158 expectedInvokedMethods: ["middleMousePaste"], 159 preventDefault: true, 160 }, 161 ]; 162 163 // Array of method names that will be replaced in the new window. 164 var gReplacedMethods = [ 165 "middleMousePaste", 166 "urlSecurityCheck", 167 "loadURI", 168 "gatherTextUnder", 169 "saveURL", 170 "openLinkIn", 171 "getShortcutOrURIAndPostData", 172 ]; 173 174 // Returns the target object for the replaced method. 175 function getStub(replacedMethod) { 176 let targetObj = 177 replacedMethod == "getShortcutOrURIAndPostData" ? UrlbarUtils : gTestWin; 178 return targetObj[replacedMethod]; 179 } 180 181 // Reference to the new window. 182 var gTestWin = null; 183 184 // The test currently running. 185 var gCurrentTest = null; 186 187 function test() { 188 waitForExplicitFinish(); 189 190 registerCleanupFunction(function () { 191 sinon.restore(); 192 }); 193 194 gTestWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank"); 195 whenDelayedStartupFinished(gTestWin, function () { 196 info("Browser window opened"); 197 waitForFocus(function () { 198 info("Browser window focused"); 199 waitForFocus( 200 function () { 201 info("Setting up browser..."); 202 setupTestBrowserWindow(); 203 info("Running tests..."); 204 executeSoon(runNextTest); 205 }, 206 gTestWin.content, 207 true 208 ); 209 }, gTestWin); 210 }); 211 } 212 213 // Click handler used to steal click events. 214 var gClickHandler = { 215 handleEvent(event) { 216 if (event.type == "click" && event.button != 0) { 217 return; 218 } 219 let linkId = event.target.id || event.target.localName; 220 let wantedEvent = gCurrentTest.wantedEvent || "click"; 221 is( 222 event.type, 223 wantedEvent, 224 `${gCurrentTest.desc}:Handler received a ${wantedEvent} event on ${linkId}` 225 ); 226 227 let isPanelClick = linkId == "panellink"; 228 gTestWin.contentAreaClick(event, isPanelClick); 229 let prevent = event.defaultPrevented; 230 is( 231 prevent, 232 gCurrentTest.preventDefault, 233 gCurrentTest.desc + 234 ": event.defaultPrevented is correct (" + 235 prevent + 236 ")" 237 ); 238 239 // Check that all required methods have been called. 240 for (let expectedMethod of gCurrentTest.expectedInvokedMethods) { 241 ok( 242 getStub(expectedMethod).called, 243 `${gCurrentTest.desc}:${expectedMethod} should have been invoked` 244 ); 245 } 246 247 for (let method of gReplacedMethods) { 248 if ( 249 getStub(method).called && 250 !gCurrentTest.expectedInvokedMethods.includes(method) 251 ) { 252 ok(false, `Should have not called ${method}`); 253 } 254 } 255 256 event.preventDefault(); 257 event.stopPropagation(); 258 259 executeSoon(runNextTest); 260 }, 261 }; 262 263 function setupTestBrowserWindow() { 264 // Steal click events and don't propagate them. 265 gTestWin.addEventListener("click", gClickHandler, true); 266 gTestWin.addEventListener("auxclick", gClickHandler, true); 267 268 // Replace methods. 269 gReplacedMethods.forEach(function (methodName) { 270 let targetObj = 271 methodName == "getShortcutOrURIAndPostData" ? UrlbarUtils : gTestWin; 272 sinon.stub(targetObj, methodName).returnsArg(0); 273 }); 274 275 // Inject links in content. 276 let doc = gTestWin.content.document; 277 let mainDiv = doc.createElement("div"); 278 mainDiv.innerHTML = 279 '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' + 280 '<p><a id="panellink" href="http://mochi.test/moz/">Panel link</a></p>' + 281 '<p><a id="emptylink">Empty link</a></p>' + 282 '<p><math id="mathlink" xmlns="http://www.w3.org/1998/Math/MathML" href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' + 283 '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>' + 284 '<p><map name="map" id="map"><area href="http://mochi.test/moz/" shape="rect" coords="0,0,128,128" /></map><img id="maplink" usemap="#map" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAABGdBTUEAALGPC%2FxhBQAAAOtJREFUeF7t0IEAAAAAgKD9qRcphAoDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGBgwIAAAT0N51AAAAAASUVORK5CYII%3D"/></p>'; 285 doc.body.appendChild(mainDiv); 286 } 287 288 function runNextTest() { 289 if (!gCurrentTest) { 290 gCurrentTest = gTests.shift(); 291 gCurrentTest.setup(); 292 } 293 294 if (!gCurrentTest.targets.length) { 295 info(gCurrentTest.desc + ": cleaning up..."); 296 gCurrentTest.clean(); 297 298 if (gTests.length) { 299 gCurrentTest = gTests.shift(); 300 gCurrentTest.setup(); 301 } else { 302 finishTest(); 303 return; 304 } 305 } 306 307 // Move to next target. 308 sinon.resetHistory(); 309 let target = gCurrentTest.targets.shift(); 310 311 info(gCurrentTest.desc + ": testing " + target); 312 313 // Fire (aux)click event. 314 let targetElt = gTestWin.content.document.getElementById(target); 315 ok(targetElt, gCurrentTest.desc + ": target is valid (" + targetElt.id + ")"); 316 EventUtils.synthesizeMouseAtCenter( 317 targetElt, 318 gCurrentTest.event, 319 gTestWin.content 320 ); 321 } 322 323 function finishTest() { 324 info("Restoring browser..."); 325 gTestWin.removeEventListener("click", gClickHandler, true); 326 gTestWin.removeEventListener("auxclick", gClickHandler, true); 327 gTestWin.close(); 328 finish(); 329 }