Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | <html xmlns="http://www.w3.org/1999/xhtml"> |
michael@0 | 2 | <!-- |
michael@0 | 3 | https://bugzilla.mozilla.org/show_bug.cgi?id=821850 |
michael@0 | 4 | --> |
michael@0 | 5 | <head> |
michael@0 | 6 | <bindings xmlns="http://www.mozilla.org/xbl"> |
michael@0 | 7 | <binding id="testBinding"> |
michael@0 | 8 | <implementation> |
michael@0 | 9 | <constructor> |
michael@0 | 10 | // Store a property as an expando on the bound element. |
michael@0 | 11 | this._prop = "propVal"; |
michael@0 | 12 | |
michael@0 | 13 | // Wait for both constructors to fire. |
michael@0 | 14 | window.constructorCount = (window.constructorCount + 1) || 1; |
michael@0 | 15 | if (window.constructorCount != 2) |
michael@0 | 16 | return; |
michael@0 | 17 | |
michael@0 | 18 | // Grab some basic infrastructure off the content window. |
michael@0 | 19 | var win = XPCNativeWrapper.unwrap(window); |
michael@0 | 20 | SpecialPowers = win.SpecialPowers; |
michael@0 | 21 | Cu = SpecialPowers.Cu; |
michael@0 | 22 | is = win.is; |
michael@0 | 23 | ok = win.ok; |
michael@0 | 24 | SimpleTest = win.SimpleTest; |
michael@0 | 25 | |
michael@0 | 26 | // Stick some expandos on the content window. |
michael@0 | 27 | window.xrayExpando = 3; |
michael@0 | 28 | win.primitiveExpando = 11; |
michael@0 | 29 | win.stringExpando = "stringExpando"; |
michael@0 | 30 | win.objectExpando = { foo: 12 }; |
michael@0 | 31 | win.globalExpando = SpecialPowers.unwrap(Cu.getGlobalForObject({})); |
michael@0 | 32 | win.functionExpando = function() { return "called" }; |
michael@0 | 33 | win.functionExpando.prop = 2; |
michael@0 | 34 | |
michael@0 | 35 | // Make sure we're Xraying. |
michael@0 | 36 | ok(Cu.isXrayWrapper(window), "Window is Xrayed"); |
michael@0 | 37 | ok(Cu.isXrayWrapper(document), "Document is Xrayed"); |
michael@0 | 38 | |
michael@0 | 39 | var bound = document.getElementById('bound'); |
michael@0 | 40 | ok(bound, "bound is non-null"); |
michael@0 | 41 | is(bound.method('baz'), "method:baz", "Xray methods work"); |
michael@0 | 42 | is(bound.prop, "propVal", "Property Xrays work"); |
michael@0 | 43 | is(bound.primitiveField, undefined, "Xrays don't show fields"); |
michael@0 | 44 | is(bound.wrappedJSObject.primitiveField, 2, "Waiving Xrays show fields"); |
michael@0 | 45 | |
michael@0 | 46 | // Check exposure behavior. |
michael@0 | 47 | is(typeof bound.unexposedMethod, 'function', |
michael@0 | 48 | "Unexposed method should be visible to XBL"); |
michael@0 | 49 | is(typeof bound.wrappedJSObject.unexposedMethod, 'undefined', |
michael@0 | 50 | "Unexposed method should not be defined in content"); |
michael@0 | 51 | is(typeof bound.unexposedProperty, 'number', |
michael@0 | 52 | "Unexposed property should be visible to XBL"); |
michael@0 | 53 | is(typeof bound.wrappedJSObject.unexposedProperty, 'undefined', |
michael@0 | 54 | "Unexposed property should not be defined in content"); |
michael@0 | 55 | |
michael@0 | 56 | // Check that here HTMLImageElement.QueryInterface works |
michael@0 | 57 | var img = document.querySelector("img"); |
michael@0 | 58 | ok("QueryInterface" in img, |
michael@0 | 59 | "Should have a img.QueryInterface here"); |
michael@0 | 60 | is(img.QueryInterface(Components.interfaces.nsIImageLoadingContent), |
michael@0 | 61 | img, "Should be able to QI the image"); |
michael@0 | 62 | |
michael@0 | 63 | // Make sure standard constructors work right in the presence of |
michael@0 | 64 | // sandboxPrototype and Xray-resolved constructors. |
michael@0 | 65 | is(window.Function, XPCNativeWrapper(window.wrappedJSObject.Function), |
michael@0 | 66 | "window.Function comes from the window, not the global"); |
michael@0 | 67 | ok(Function != window.Function, "Function constructors are distinct"); |
michael@0 | 68 | is(Object.getPrototypeOf(Function.prototype), Object.getPrototypeOf({foo: 42}), |
michael@0 | 69 | "Function constructor is local"); |
michael@0 | 70 | |
michael@0 | 71 | // This gets invoked by an event handler. |
michael@0 | 72 | window.finish = function() { |
michael@0 | 73 | // Content messed with stuff. Make sure we still see the right thing. |
michael@0 | 74 | is(bound.method('bay'), "method:bay", "Xray methods work"); |
michael@0 | 75 | is(bound.wrappedJSObject.method('bay'), "hah", "Xray waived methods work"); |
michael@0 | 76 | is(bound.prop, "set:someOtherVal", "Xray props work"); |
michael@0 | 77 | is(bound.wrappedJSObject.prop, "redefined", "Xray waived props work"); |
michael@0 | 78 | is(bound.wrappedJSObject.primitiveField, 321, "Can't do anything about redefined fields"); |
michael@0 | 79 | |
michael@0 | 80 | SimpleTest.finish(); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | // Hand things off to content. Content will call us back. |
michael@0 | 84 | win.go(); |
michael@0 | 85 | </constructor> |
michael@0 | 86 | <field name="primitiveField">2</field> |
michael@0 | 87 | <method name="unexposedMethod"><body></body></method> |
michael@0 | 88 | <property name="unexposedProperty" onget="return 2;" readonly="true"></property> |
michael@0 | 89 | <method name="method" exposeToUntrustedContent="true"> |
michael@0 | 90 | <parameter name="arg" /> |
michael@0 | 91 | <body> |
michael@0 | 92 | return "method:" + arg; |
michael@0 | 93 | </body> |
michael@0 | 94 | </method> |
michael@0 | 95 | <method name="passMeAJSObject" exposeToUntrustedContent="true"> |
michael@0 | 96 | <parameter name="arg" /> |
michael@0 | 97 | <body> |
michael@0 | 98 | is(typeof arg.prop, 'undefined', "No properties"); |
michael@0 | 99 | is(Object.getOwnPropertyNames(arg).length, 0, "Should have no own properties"); |
michael@0 | 100 | try { |
michael@0 | 101 | arg.foo = 2; |
michael@0 | 102 | ok(true, "Stuff fails silently"); |
michael@0 | 103 | } catch (e) { |
michael@0 | 104 | ok(false, "Stuff should fail silently"); |
michael@0 | 105 | } |
michael@0 | 106 | is(typeof arg.foo, 'undefined', "Shouldn't place props"); |
michael@0 | 107 | </body> |
michael@0 | 108 | </method> |
michael@0 | 109 | <property name="prop" exposeToUntrustedContent="true"> |
michael@0 | 110 | <getter>return this._prop;</getter> |
michael@0 | 111 | <setter>this._prop = "set:" + val;</setter> |
michael@0 | 112 | </property> |
michael@0 | 113 | </implementation> |
michael@0 | 114 | <handlers> |
michael@0 | 115 | <handler event="testevent" action="ok(true, 'called event handler'); finish();" allowuntrusted="true"/> |
michael@0 | 116 | <handler event="testtrusted" action="ok(true, 'called trusted handler'); window.wrappedJSObject.triggeredTrustedHandler = true;"/> |
michael@0 | 117 | <handler event="keyup" action="ok(true, 'called untrusted key handler'); window.wrappedJSObject.triggeredUntrustedKeyHandler = true;" allowuntrusted="true"/> |
michael@0 | 118 | <handler event="keydown" action="ok(true, 'called trusted key handler'); window.wrappedJSObject.triggeredTrustedKeyHandler = true;"/> |
michael@0 | 119 | </handlers> |
michael@0 | 120 | </binding> |
michael@0 | 121 | </bindings> |
michael@0 | 122 | <script type="application/javascript"> |
michael@0 | 123 | <![CDATA[ |
michael@0 | 124 | |
michael@0 | 125 | ok = parent.ok; |
michael@0 | 126 | is = parent.is; |
michael@0 | 127 | SimpleTest = parent.SimpleTest; |
michael@0 | 128 | SpecialPowers = parent.SpecialPowers; |
michael@0 | 129 | |
michael@0 | 130 | // Test the Xray waiving behavior when accessing fields. We should be able to |
michael@0 | 131 | // see sequential JS-implemented properties, but should regain Xrays when we |
michael@0 | 132 | // hit a native property. |
michael@0 | 133 | window.contentVal = { foo: 10, rabbit: { hole: { bar: 100, win: window} } }; |
michael@0 | 134 | ok(true, "Set contentVal"); |
michael@0 | 135 | |
michael@0 | 136 | // Check that we're not exposing QueryInterface to non-XBL code |
michael@0 | 137 | ok(!("QueryInterface" in document), |
michael@0 | 138 | "Should not have a document.QueryInterface here"); |
michael@0 | 139 | |
michael@0 | 140 | function go() { |
michael@0 | 141 | "use strict"; |
michael@0 | 142 | |
michael@0 | 143 | // Test what we can and cannot access in the XBL scope. |
michael@0 | 144 | is(typeof window.xrayExpando, "undefined", "Xray expandos are private to the caller"); |
michael@0 | 145 | is(window.primitiveExpando, 11, "Can see waived expandos"); |
michael@0 | 146 | is(window.stringExpando, "stringExpando", "Can see waived expandos"); |
michael@0 | 147 | is(typeof window.objectExpando, "object", "object expando exists"); |
michael@0 | 148 | checkThrows(function() window.objectExpando.foo); |
michael@0 | 149 | is(SpecialPowers.wrap(window.objectExpando).foo, 12, "SpecialPowers sees the right thing"); |
michael@0 | 150 | is(typeof window.globalExpando, "object", "Can see global object"); |
michael@0 | 151 | checkThrows(function() window.globalExpando.win); |
michael@0 | 152 | is(window.functionExpando(), "called", "XBL functions are callable"); |
michael@0 | 153 | checkThrows(function() window.functionExpando.prop); |
michael@0 | 154 | |
michael@0 | 155 | // Inspect the bound element. |
michael@0 | 156 | var bound = document.getElementById('bound'); |
michael@0 | 157 | is(bound.primitiveField, 2, "Can see primitive fields"); |
michael@0 | 158 | is(bound.method("foo"), "method:foo", "Can invoke XBL method from content"); |
michael@0 | 159 | is(bound.prop, "propVal", "Can access properties from content"); |
michael@0 | 160 | bound.prop = "someOtherVal"; |
michael@0 | 161 | is(bound.prop, "set:someOtherVal", "Can set properties from content"); |
michael@0 | 162 | |
michael@0 | 163 | // Make sure we can't pass JS objects to the XBL scope. |
michael@0 | 164 | var proto = bound.__proto__; |
michael@0 | 165 | proto.passMeAJSObject({prop: 2}); |
michael@0 | 166 | |
michael@0 | 167 | // |
michael@0 | 168 | // Try sticking a bunch of stuff on the prototype object. |
michael@0 | 169 | // |
michael@0 | 170 | |
michael@0 | 171 | proto.someExpando = 201; |
michael@0 | 172 | is(bound.someExpando, 201, "Can stick non-XBL properties on the proto"); |
michael@0 | 173 | |
michael@0 | 174 | // Previously, this code checked that content couldn't tamper with its XBL |
michael@0 | 175 | // prototype. But we decided to allow this to reduce regression risk, so for |
michael@0 | 176 | // now just check that this works. |
michael@0 | 177 | function checkMayTamper(obj, propName, desc) { |
michael@0 | 178 | var accessor = !('value' in Object.getOwnPropertyDescriptor(obj, propName)); |
michael@0 | 179 | if (!accessor) |
michael@0 | 180 | checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign"); |
michael@0 | 181 | checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, value: 3}) }, desc + ": define with value"); |
michael@0 | 182 | checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, writable: true}) }, desc + ": make writable"); |
michael@0 | 183 | checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true}) }, desc + ": make configurable"); |
michael@0 | 184 | checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, get: function() {}}) }, desc + ": define with getter"); |
michael@0 | 185 | checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, set: function() {}}) }, desc + ": define with setter"); |
michael@0 | 186 | |
michael@0 | 187 | // Windows are implemented as proxies, and Proxy::delete_ doesn't currently |
michael@0 | 188 | // pass strict around. Work around it in the window.binding case by just |
michael@0 | 189 | // checking if delete returns false. |
michael@0 | 190 | // manually. |
michael@0 | 191 | checkAllowed(function() { delete obj[propName]; }, desc + ": delete"); |
michael@0 | 192 | |
michael@0 | 193 | if (!accessor) |
michael@0 | 194 | checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign (again)"); |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | // Make sure content can do whatever it wants with the prototype. |
michael@0 | 198 | checkMayTamper(proto, 'method', "XBL Proto Method"); |
michael@0 | 199 | checkMayTamper(proto, 'prop', "XBL Proto Prop"); |
michael@0 | 200 | checkMayTamper(proto, 'primitiveField', "XBL Field Accessor"); |
michael@0 | 201 | |
michael@0 | 202 | // Tamper with the derived object. This doesn't affect the XBL scope thanks |
michael@0 | 203 | // to Xrays. |
michael@0 | 204 | bound.method = function() { return "heh"; }; |
michael@0 | 205 | Object.defineProperty(bound, 'method', {value: function() { return "hah" }}); |
michael@0 | 206 | Object.defineProperty(bound, 'prop', {value: "redefined"}); |
michael@0 | 207 | bound.primitiveField = 321; |
michael@0 | 208 | |
michael@0 | 209 | // We need a chrome window to create trusted events. This isn't really doable |
michael@0 | 210 | // in child processes, so let's just skip if that's the case. |
michael@0 | 211 | if (SpecialPowers.isMainProcess()) { |
michael@0 | 212 | var Ci = SpecialPowers.Ci; |
michael@0 | 213 | var chromeWin = SpecialPowers.wrap(window.top) |
michael@0 | 214 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 215 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 216 | .QueryInterface(Ci.nsIDocShell) |
michael@0 | 217 | .chromeEventHandler.ownerDocument.defaultView; |
michael@0 | 218 | |
michael@0 | 219 | // Untrusted events should not trigger event handlers without |
michael@0 | 220 | // exposeToUntrustedContent=true. |
michael@0 | 221 | window.triggeredTrustedHandler = false; |
michael@0 | 222 | var untrustedEvent = new CustomEvent('testtrusted'); |
michael@0 | 223 | ok(!untrustedEvent.isTrusted, "Created an untrusted event"); |
michael@0 | 224 | is(untrustedEvent.type, 'testtrusted', "Constructor should see type"); |
michael@0 | 225 | bound.dispatchEvent(untrustedEvent); |
michael@0 | 226 | ok(!window.triggeredTrustedHandler, "untrusted events should not trigger trusted handler"); |
michael@0 | 227 | var trustedEvent = new chromeWin.CustomEvent('testtrusted'); |
michael@0 | 228 | ok(trustedEvent.isTrusted, "Created a trusted event"); |
michael@0 | 229 | is(trustedEvent.type, 'testtrusted', "Wrapped constructor should see type"); |
michael@0 | 230 | SpecialPowers.wrap(bound).dispatchEvent(trustedEvent); |
michael@0 | 231 | ok(window.triggeredTrustedHandler, "trusted events should trigger trusted handler"); |
michael@0 | 232 | |
michael@0 | 233 | // |
michael@0 | 234 | // We check key events as well, since they're implemented differently. |
michael@0 | 235 | // |
michael@0 | 236 | // NB: We don't check isTrusted on the events we create here, because |
michael@0 | 237 | // according to smaug, old-style event initialization doesn't mark the |
michael@0 | 238 | // event as trusted until it's dispatched. |
michael@0 | 239 | // |
michael@0 | 240 | |
michael@0 | 241 | window.triggeredUntrustedKeyHandler = false; |
michael@0 | 242 | window.triggeredTrustedKeyHandler = false; |
michael@0 | 243 | |
michael@0 | 244 | // Untrusted event, permissive handler. |
michael@0 | 245 | var untrustedKeyEvent = document.createEvent('KeyboardEvent'); |
michael@0 | 246 | untrustedKeyEvent.initEvent('keyup', true, true); |
michael@0 | 247 | bound.dispatchEvent(untrustedKeyEvent); |
michael@0 | 248 | ok(window.triggeredUntrustedKeyHandler, "untrusted key events should trigger untrusted handler"); |
michael@0 | 249 | |
michael@0 | 250 | // Untrusted event, strict handler. |
michael@0 | 251 | var fakeTrustedKeyEvent = document.createEvent('KeyboardEvent'); |
michael@0 | 252 | fakeTrustedKeyEvent.initEvent('keydown', true, true); |
michael@0 | 253 | bound.dispatchEvent(fakeTrustedKeyEvent); |
michael@0 | 254 | ok(!window.triggeredTrustedKeyHandler, "untrusted key events should not trigger trusted handler"); |
michael@0 | 255 | |
michael@0 | 256 | // Trusted event, strict handler. |
michael@0 | 257 | var trustedKeyEvent = chromeWin.document.createEvent('KeyboardEvent'); |
michael@0 | 258 | trustedKeyEvent.initEvent('keydown', true, true); |
michael@0 | 259 | SpecialPowers.wrap(bound).dispatchEvent(trustedKeyEvent); |
michael@0 | 260 | ok(window.triggeredTrustedKeyHandler, "trusted key events should trigger trusted handler"); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | // Hand control back to the XBL scope by dispatching an event on the bound element. |
michael@0 | 264 | bound.dispatchEvent(new CustomEvent('testevent')); |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | function checkThrows(fn) { |
michael@0 | 268 | try { fn(); ok(false, "Should have thrown"); } |
michael@0 | 269 | catch (e) { ok(!!/denied|insecure/.exec(e), "Should have thrown security exception: " + e); } |
michael@0 | 270 | } |
michael@0 | 271 | |
michael@0 | 272 | function checkAllowed(fn, desc) { |
michael@0 | 273 | try { fn(); ok(true, desc + ": Didn't throw"); } |
michael@0 | 274 | catch (e) { ok(false, desc + ": Threw: " + e); } |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | function setup() { |
michael@0 | 278 | // When the bindings are applied, the constructor will be invoked and the |
michael@0 | 279 | // test will continue. |
michael@0 | 280 | document.getElementById('bound').style.MozBinding = 'url(#testBinding)'; |
michael@0 | 281 | document.getElementById('bound2').style.MozBinding = 'url(#testBinding)'; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | ]]> |
michael@0 | 285 | </script> |
michael@0 | 286 | </head> |
michael@0 | 287 | <body onload="setup()"> |
michael@0 | 288 | <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=821850">Mozilla Bug 821850</a> |
michael@0 | 289 | <p id="display"></p> |
michael@0 | 290 | <div id="content"> |
michael@0 | 291 | <div id="bound">Bound element</div> |
michael@0 | 292 | <div id="bound2">Bound element</div> |
michael@0 | 293 | <img/> |
michael@0 | 294 | </div> |
michael@0 | 295 | <pre id="test"> |
michael@0 | 296 | </pre> |
michael@0 | 297 | </body> |
michael@0 | 298 | </html> |