browser_jsonview_data_blocking.js (5739B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const TEST_PATH = getRootDirectory(gTestPath).replace( 7 "chrome://mochitests/content", 8 "http://example.com" 9 ); 10 11 const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view"; 12 const nullP = Services.scriptSecurityManager.createNullPrincipal({}); 13 14 // We need 3 levels of nesting just to get to run something against a content 15 // page, so the devtools limit of 4 levels of nesting don't help: 16 /* eslint max-nested-callbacks: 0 */ 17 18 /** 19 * Check that we don't expose a JSONView object on data: URI windows where 20 * we block the load. 21 */ 22 add_task(async function test_blocked_data_exposure() { 23 await SpecialPowers.pushPrefEnv({ 24 set: [["security.data_uri.block_toplevel_data_uri_navigations", true]], 25 }); 26 await BrowserTestUtils.withNewTab(TEST_PATH + "empty.html", async browser => { 27 const tabCount = gBrowser.tabs.length; 28 await SpecialPowers.spawn(browser, [], function () { 29 content.w = content.window.open( 30 "data:application/vnd.mozilla.json.view,1", 31 "_blank" 32 ); 33 ok( 34 !Cu.waiveXrays(content.w).JSONView, 35 "Should not have created a JSON View object" 36 ); 37 // We need to wait for the JSON view machinery to actually have a chance to run. 38 // We have no way to detect that it has or hasn't, so a setTimeout is the best we 39 // can do, unfortunately. 40 return new Promise(resolve => { 41 content.setTimeout(function () { 42 // Putting the resolve before the check to avoid JS errors potentially causing 43 // the test to time out. 44 resolve(); 45 ok( 46 !Cu.waiveXrays(content.w).JSONView, 47 "Should still not have a JSON View object" 48 ); 49 }, 1000); 50 }); 51 }); 52 // Without this, if somehow the data: protocol blocker stops working, the 53 // test would just keep passing. 54 is( 55 tabCount, 56 gBrowser.tabs.length, 57 "Haven't actually opened a new window/tab" 58 ); 59 }); 60 }); 61 62 /** 63 * Check that aborted channels also abort sending data from the stream converter. 64 */ 65 add_task(async function test_converter_abort_should_stop_data_sending() { 66 const loadInfo = NetUtil.newChannel({ 67 uri: Services.io.newURI("data:text/plain,"), 68 loadingPrincipal: nullP, 69 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 70 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, 71 }).loadInfo; 72 // Stub all the things. 73 const chan = { 74 QueryInterface: ChromeUtils.generateQI([ 75 "nsIChannel", 76 "nsIWritablePropertyBag", 77 ]), 78 URI: Services.io.newURI("data:application/json,{}"), 79 // loadinfo is builtinclass, need to actually have one: 80 loadInfo, 81 notificationCallbacks: { 82 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 83 getInterface() { 84 // We want a loadcontext here, which is also builtinclass, can't stub. 85 return docShell; 86 }, 87 }, 88 status: Cr.NS_OK, 89 setProperty() {}, 90 }; 91 let onStartFired = false; 92 const listener = { 93 QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), 94 onStartRequest() { 95 onStartFired = true; 96 // This should force the converter to abort, too: 97 chan.status = Cr.NS_BINDING_ABORTED; 98 }, 99 onDataAvailable() { 100 ok(false, "onDataAvailable should never fire"); 101 }, 102 }; 103 const conv = Cc[ 104 "@mozilla.org/streamconv;1?from=" + JSON_VIEW_MIME_TYPE + "&to=*/*" 105 ].createInstance(Ci.nsIStreamConverter); 106 conv.asyncConvertData( 107 "application/vnd.mozilla.json.view", 108 "text/html", 109 listener, 110 null 111 ); 112 conv.onStartRequest(chan); 113 ok(onStartFired, "Should have fired onStartRequest"); 114 }); 115 116 /** 117 * Check that principal mismatches break things. Note that we're stubbing 118 * the window associated with the channel to be a browser window; the 119 * converter should be bailing out because the window's principal won't 120 * match the null principal to which the converter tries to reset the 121 * inheriting principal of the channel. 122 */ 123 add_task(async function test_converter_principal_needs_matching() { 124 const loadInfo = NetUtil.newChannel({ 125 uri: Services.io.newURI("data:text/plain,"), 126 loadingPrincipal: nullP, 127 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 128 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, 129 }).loadInfo; 130 // Stub all the things. 131 const chan = { 132 QueryInterface: ChromeUtils.generateQI([ 133 "nsIChannel", 134 "nsIWritablePropertyBag", 135 ]), 136 URI: Services.io.newURI("data:application/json,{}"), 137 // loadinfo is builtinclass, need to actually have one: 138 loadInfo, 139 notificationCallbacks: { 140 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 141 getInterface() { 142 // We want a loadcontext here, which is also builtinclass, can't stub. 143 return docShell; 144 }, 145 }, 146 status: Cr.NS_OK, 147 setProperty() {}, 148 cancel(arg) { 149 this.status = arg; 150 }, 151 }; 152 let onStartFired = false; 153 const listener = { 154 QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), 155 onStartRequest() { 156 onStartFired = true; 157 }, 158 onDataAvailable() { 159 ok(false, "onDataAvailable should never fire"); 160 }, 161 }; 162 const conv = Cc[ 163 "@mozilla.org/streamconv;1?from=" + JSON_VIEW_MIME_TYPE + "&to=*/*" 164 ].createInstance(Ci.nsIStreamConverter); 165 conv.asyncConvertData( 166 "application/vnd.mozilla.json.view", 167 "text/html", 168 listener, 169 null 170 ); 171 conv.onStartRequest(chan); 172 ok(onStartFired, "Should have fired onStartRequest"); 173 is(chan.status, Cr.NS_BINDING_ABORTED, "Should have been aborted."); 174 });