1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/browser-element/mochitest/browserElement_ContextmenuEvents.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,289 @@ 1.4 +'use strict'; 1.5 + 1.6 +SimpleTest.waitForExplicitFinish(); 1.7 +browserElementTestHelpers.setEnabledPref(true); 1.8 +browserElementTestHelpers.addPermission(); 1.9 + 1.10 +let audioUrl = 'http://mochi.test:8888/tests/dom/browser-element/mochitest/audio.ogg'; 1.11 +let videoUrl = 'http://mochi.test:8888/tests/dom/browser-element/mochitest/short-video.ogv'; 1.12 + 1.13 +function runTests() { 1.14 + createIframe(function onIframeLoaded() { 1.15 + checkEmptyContextMenu(); 1.16 + }); 1.17 +} 1.18 + 1.19 +function checkEmptyContextMenu() { 1.20 + sendContextMenuTo('body', function onContextMenu(detail) { 1.21 + is(detail.contextmenu, null, 'Body context clicks have no context menu'); 1.22 + 1.23 + checkInnerContextMenu(); 1.24 + }); 1.25 +} 1.26 + 1.27 +function checkInnerContextMenu() { 1.28 + sendContextMenuTo('#inner-link', function onContextMenu(detail) { 1.29 + is(detail.systemTargets.length, 1, 'Includes anchor data'); 1.30 + is(detail.contextmenu.items.length, 2, 'Inner clicks trigger correct menu'); 1.31 + 1.32 + checkCustomContextMenu(); 1.33 + }); 1.34 +} 1.35 + 1.36 +function checkCustomContextMenu() { 1.37 + sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) { 1.38 + is(detail.contextmenu.items.length, 2, 'trigger custom contextmenu'); 1.39 + 1.40 + checkNestedContextMenu(); 1.41 + }); 1.42 +} 1.43 + 1.44 +function checkNestedContextMenu() { 1.45 + sendContextMenuTo('#menu2-trigger', function onContextMenu(detail) { 1.46 + var innerMenu = detail.contextmenu.items.filter(function(x) { 1.47 + return x.type === 'menu'; 1.48 + }); 1.49 + is(detail.systemTargets.length, 2, 'Includes anchor and img data'); 1.50 + ok(innerMenu.length > 0, 'Menu contains a nested menu'); 1.51 + 1.52 + checkPreviousContextMenuHandler(); 1.53 + }); 1.54 +} 1.55 + 1.56 + // Finished testing the data passed to the contextmenu handler, 1.57 + // now we start selecting contextmenu items 1.58 +function checkPreviousContextMenuHandler() { 1.59 + // This is previously triggered contextmenu data, since we have 1.60 + // fired subsequent contextmenus this should not be mistaken 1.61 + // for a current menuitem 1.62 + var detail = previousContextMenuDetail; 1.63 + var previousId = detail.contextmenu.items[0].id; 1.64 + checkContextMenuCallbackForId(detail, previousId, function onCallbackFired(label) { 1.65 + is(label, null, 'Callback label should be empty since this handler is old'); 1.66 + 1.67 + checkCurrentContextMenuHandler(); 1.68 + }); 1.69 +} 1.70 + 1.71 +function checkCurrentContextMenuHandler() { 1.72 + // This triggers a current menuitem 1.73 + var detail = currentContextMenuDetail; 1.74 + 1.75 + var innerMenu = detail.contextmenu.items.filter(function(x) { 1.76 + return x.type === 'menu'; 1.77 + }); 1.78 + 1.79 + var currentId = innerMenu[0].items[1].id; 1.80 + checkContextMenuCallbackForId(detail, currentId, function onCallbackFired(label) { 1.81 + is(label, 'inner 2', 'Callback label should be set correctly'); 1.82 + 1.83 + checkAgainCurrentContextMenuHandler(); 1.84 + }); 1.85 +} 1.86 + 1.87 +function checkAgainCurrentContextMenuHandler() { 1.88 + // Once an item it selected, subsequent selections are ignored 1.89 + var detail = currentContextMenuDetail; 1.90 + 1.91 + var innerMenu = detail.contextmenu.items.filter(function(x) { 1.92 + return x.type === 'menu'; 1.93 + }); 1.94 + 1.95 + var currentId = innerMenu[0].items[1].id; 1.96 + checkContextMenuCallbackForId(detail, currentId, function onCallbackFired(label) { 1.97 + is(label, null, 'Callback label should be empty since this handler has already been used'); 1.98 + 1.99 + checkCallbackWithPreventDefault(); 1.100 + }); 1.101 +}; 1.102 + 1.103 +// Finished testing callbacks if the embedder calls preventDefault() on the 1.104 +// mozbrowsercontextmenu event, now we start checking for some cases where the embedder 1.105 +// does not want to call preventDefault() for some reasons. 1.106 +function checkCallbackWithPreventDefault() { 1.107 + sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) { 1.108 + var id = detail.contextmenu.items[0].id; 1.109 + checkContextMenuCallbackForId(detail, id, function onCallbackFired(label) { 1.110 + is(label, 'foo', 'Callback label should be set correctly'); 1.111 + 1.112 + checkCallbackWithoutPreventDefault(); 1.113 + }); 1.114 + }); 1.115 +} 1.116 + 1.117 +function checkCallbackWithoutPreventDefault() { 1.118 + sendContextMenuTo('#menu1-trigger', function onContextMenu(detail) { 1.119 + var id = detail.contextmenu.items[0].id; 1.120 + checkContextMenuCallbackForId(detail, id, function onCallbackFired(label) { 1.121 + is(label, null, 'Callback label should be null'); 1.122 + 1.123 + checkImageContextMenu(); 1.124 + }); 1.125 + }, /* ignorePreventDefault */ true); 1.126 +} 1.127 + 1.128 +function checkImageContextMenu() { 1.129 + sendContextMenuTo('#menu3-trigger', function onContextMenu(detail) { 1.130 + var target = detail.systemTargets[0]; 1.131 + is(target.nodeName, 'IMG', 'Reports correct nodeName'); 1.132 + is(target.data.uri, 'example.png', 'Reports correct uri'); 1.133 + 1.134 + checkVideoContextMenu(); 1.135 + }, /* ignorePreventDefault */ true); 1.136 +} 1.137 + 1.138 +function checkVideoContextMenu() { 1.139 + sendContextMenuTo('#menu4-trigger', function onContextMenu(detail) { 1.140 + var target = detail.systemTargets[0]; 1.141 + is(target.nodeName, 'VIDEO', 'Reports correct nodeName'); 1.142 + is(target.data.uri, videoUrl, 'Reports uri correctly in data'); 1.143 + is(target.data.hasVideo, true, 'Video data in video tag does "hasVideo"'); 1.144 + 1.145 + checkAudioContextMenu(); 1.146 + }, /* ignorePreventDefault */ true); 1.147 +} 1.148 + 1.149 +function checkAudioContextMenu() { 1.150 + sendContextMenuTo('#menu6-trigger', function onContextMenu(detail) { 1.151 + var target = detail.systemTargets[0]; 1.152 + is(target.nodeName, 'AUDIO', 'Reports correct nodeName'); 1.153 + is(target.data.uri, audioUrl, 'Reports uri correctly in data'); 1.154 + 1.155 + checkAudioinVideoContextMenu(); 1.156 + }, /* ignorePreventDefault */ true); 1.157 +} 1.158 + 1.159 +function checkAudioinVideoContextMenu() { 1.160 + sendSrcTo('#menu5-trigger', audioUrl, function onSrcSet() { 1.161 + sendContextMenuTo('#menu5-trigger', function onContextMenu(detail) { 1.162 + var target = detail.systemTargets[0]; 1.163 + is(target.nodeName, 'VIDEO', 'Reports correct nodeName'); 1.164 + is(target.data.uri, audioUrl, 'Reports uri correctly in data'); 1.165 + is(target.data.hasVideo, false, 'Audio data in video tag reports no "hasVideo"'); 1.166 + 1.167 + SimpleTest.finish(); 1.168 + }, /* ignorePreventDefault */ true); 1.169 + }); 1.170 +} 1.171 + 1.172 +/* Helpers */ 1.173 +var mm = null; 1.174 +var previousContextMenuDetail = null; 1.175 +var currentContextMenuDetail = null; 1.176 + 1.177 +function sendSrcTo(selector, src, callback) { 1.178 + mm.sendAsyncMessage('setsrc', { 'selector': selector, 'src': src }); 1.179 + mm.addMessageListener('test:srcset', function onSrcSet(msg) { 1.180 + mm.removeMessageListener('test:srcset', onSrcSet); 1.181 + callback(); 1.182 + }); 1.183 +} 1.184 + 1.185 +function sendContextMenuTo(selector, callback, ignorePreventDefault) { 1.186 + iframe.addEventListener('mozbrowsercontextmenu', function oncontextmenu(e) { 1.187 + iframe.removeEventListener(e.type, oncontextmenu); 1.188 + 1.189 + // The embedder should call preventDefault() on the event if it will handle 1.190 + // it. Not calling preventDefault() means it won't handle the event and 1.191 + // should not be able to deal with context menu callbacks. 1.192 + if (ignorePreventDefault !== true) { 1.193 + e.preventDefault(); 1.194 + } 1.195 + 1.196 + // Keep a reference to previous/current contextmenu event details. 1.197 + previousContextMenuDetail = currentContextMenuDetail; 1.198 + currentContextMenuDetail = e.detail; 1.199 + 1.200 + setTimeout(function() { callback(e.detail); }); 1.201 + }); 1.202 + 1.203 + mm.sendAsyncMessage('contextmenu', { 'selector': selector }); 1.204 +} 1.205 + 1.206 +function checkContextMenuCallbackForId(detail, id, callback) { 1.207 + mm.addMessageListener('test:callbackfired', function onCallbackFired(msg) { 1.208 + mm.removeMessageListener('test:callbackfired', onCallbackFired); 1.209 + 1.210 + msg = SpecialPowers.wrap(msg); 1.211 + setTimeout(function() { callback(msg.data.label); }); 1.212 + }); 1.213 + 1.214 + detail.contextMenuItemSelected(id); 1.215 +} 1.216 + 1.217 + 1.218 +var iframe = null; 1.219 +function createIframe(callback) { 1.220 + iframe = document.createElement('iframe'); 1.221 + SpecialPowers.wrap(iframe).mozbrowser = true; 1.222 + 1.223 + iframe.src = 'data:text/html,<html>' + 1.224 + '<body>' + 1.225 + '<menu type="context" id="menu1" label="firstmenu">' + 1.226 + '<menuitem label="foo" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' + 1.227 + '<menuitem label="bar" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' + 1.228 + '</menu>' + 1.229 + '<menu type="context" id="menu2" label="secondmenu">' + 1.230 + '<menuitem label="outer" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' + 1.231 + '<menu>' + 1.232 + '<menuitem label="inner 1"></menuitem>' + 1.233 + '<menuitem label="inner 2" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' + 1.234 + '</menu>' + 1.235 + '</menu>' + 1.236 + '<div id="menu1-trigger" contextmenu="menu1"><a id="inner-link" href="foo.html">Menu 1</a></div>' + 1.237 + '<a href="bar.html" contextmenu="menu2"><img id="menu2-trigger" src="example.png" /></a>' + 1.238 + '<img id="menu3-trigger" src="example.png" />' + 1.239 + '<video id="menu4-trigger" src="' + videoUrl + '"></video>' + 1.240 + '<video id="menu5-trigger" preload="metadata"></video>' + 1.241 + '<audio id="menu6-trigger" src="' + audioUrl + '"></audio>' + 1.242 + '</body></html>'; 1.243 + document.body.appendChild(iframe); 1.244 + 1.245 + // The following code will be included in the child 1.246 + // ========================================================================= 1.247 + function iframeScript() { 1.248 + addMessageListener('contextmenu', function onContextMenu(msg) { 1.249 + var document = content.document; 1.250 + var evt = document.createEvent('HTMLEvents'); 1.251 + evt.initEvent('contextmenu', true, true); 1.252 + document.querySelector(msg.data.selector).dispatchEvent(evt); 1.253 + }); 1.254 + 1.255 + addMessageListener('setsrc', function onContextMenu(msg) { 1.256 + var wrappedTarget = content.document.querySelector(msg.data.selector); 1.257 + var target = XPCNativeWrapper.unwrap(wrappedTarget); 1.258 + target.addEventListener('loadedmetadata', function() { 1.259 + sendAsyncMessage('test:srcset'); 1.260 + }); 1.261 + target.src = msg.data.src; 1.262 + }); 1.263 + 1.264 + addMessageListener('browser-element-api:call', function onCallback(msg) { 1.265 + if (msg.data.msg_name != 'fire-ctx-callback') 1.266 + return; 1.267 + 1.268 + /* Use setTimeout in order to react *after* the platform */ 1.269 + content.setTimeout(function() { 1.270 + sendAsyncMessage('test:callbackfired', { label: label }); 1.271 + label = null; 1.272 + }); 1.273 + }); 1.274 + 1.275 + var label = null; 1.276 + XPCNativeWrapper.unwrap(content).onContextMenuCallbackFired = function(e) { 1.277 + label = e.target.getAttribute('label'); 1.278 + }; 1.279 + } 1.280 + // ========================================================================= 1.281 + 1.282 + iframe.addEventListener('mozbrowserloadend', function onload(e) { 1.283 + iframe.removeEventListener(e.type, onload); 1.284 + mm = SpecialPowers.getBrowserFrameMessageManager(iframe); 1.285 + mm.loadFrameScript('data:,(' + iframeScript.toString() + ')();', false); 1.286 + 1.287 + // Now we're ready, let's start testing. 1.288 + callback(); 1.289 + }); 1.290 +} 1.291 + 1.292 +addEventListener('testready', runTests);