tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

002.html (18072B)


      1 <!doctype html>
      2 <html>
      3    <head>
      4        <title>history.replaceState tests</title>
      5        <script type="text/javascript" src="/resources/testharness.js"></script>
      6        <script type="text/javascript" src="/resources/testharnessreport.js"></script>
      7        <script type="text/javascript">
      8 //does not test for firing of popstate onload, because this was dropped from the specification on 25 March 2011
      9 //covers history.state after load, in accordance with the specification draft from 25 March 2011
     10 //history.state before load is tested in 006 and 007
     11 //does not test for structured cloning of FileList, File or Blob interfaces, as these require manual file selection
     12 
     13 //**This test assumes that assignments to location.hash will be synchronous - this is how all browsers implement it.
     14 //The spec (as of 25 March 2011) disagrees.
     15 
     16 var histlength, atstep = 0, lasttimer;
     17 setup({explicit_done:true}); //tests should take under 6 seconds + execution time
     18 
     19 window.onload = function () {
     20    if( location.protocol == 'file:' ) {
     21        document.getElementsByTagName('p')[0].innerHTML = 'ERROR: This test cannot be run from file: (URL resolving will not work). It must be loaded over HTTP.';
     22        return;
     23    } else if( location.protocol == 'https:' ) {
     24        document.getElementsByTagName('p')[0].innerHTML += '<br>WARNING: Browsers may intentionally fail to update history.length when pages are loaded over HTTPS, as a privacy restriction. If possible, load this page over HTTP.';
     25    }
     26    //use a timeout, because some browsers intentionally do not add history entries for URL changes in the onload thread
     27    setTimeout(testinit,100);
     28 };
     29 function testinit() {
     30    atstep = 1;
     31    histlength = history.length;
     32    iframe = document.getElementsByTagName('iframe')[0].src = 'blank2.html';
     33    //reportload will now be called by the onload handler for the iframe
     34 }
     35 function reportload() {
     36    var iframe = document.getElementsByTagName('iframe')[0], hashchng = false;
     37    var canvassup = false, cloneobj;
     38 
     39    async function tests1() {
     40        test(function () { assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); }, 'history.length should update when loading pages in an iframe');
     41        histlength = history.length;
     42        let hashchange = new Promise(function(resolve) {
     43          iframe.contentWindow.addEventListener("hashchange", resolve, {once: true});
     44        });
     45        iframe.contentWindow.location.hash = 'test'; //should be synchronous **SEE COMMENT AT TOP OF FILE
     46        test(function () {
     47            assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' );
     48        }, 'history.length should update when setting location.hash');
     49        test(function () { assert_true( !!history.replaceState, 'critical test; ignore any failures after this' ); }, 'history.replaceState must exist'); //assert_own_property does not allow prototype inheritance
     50        test(function () { assert_true( !!iframe.contentWindow.history.replaceState, 'critical test; ignore any failures after this' ); }, 'history.replaceState must exist within iframes');
     51        test(function () {
     52            assert_equals( iframe.contentWindow.history.state, null );
     53        }, 'initial history.state should be null');
     54 
     55        await hashchange;
     56        hashchange = new Promise(function(resolve) {
     57          iframe.contentWindow.addEventListener("hashchange", resolve, {once: true});
     58        });
     59        iframe.contentWindow.location.hash = 'test2';
     60        await hashchange;
     61 
     62        iframe.contentWindow.addEventListener("hashchange", tests2, {once: true});
     63        history.back();
     64    }
     65    function tests2() {
     66        test(function () {
     67            histlength = history.length;
     68            iframe.contentWindow.history.replaceState('','');
     69            assert_equals( history.length, histlength );
     70        }, 'history.length should not update when replacing a state with no URL');
     71        test(function () {
     72            assert_equals( iframe.contentWindow.history.state, '' );
     73        }, 'history.state should update after a state is pushed');
     74        test(function () {
     75            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' );
     76        }, 'hash should not change when replaceState is called without a URL');
     77        test(function () {
     78            histlength = history.length;
     79            iframe.contentWindow.history.replaceState('','','#test3');
     80            assert_equals( history.length, histlength );
     81        }, 'history.length should not update when replacing a state with a URL');
     82        test(function () {
     83            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' );
     84        }, 'hash should change when replaceState is called with a URL');
     85        iframe.contentWindow.addEventListener("hashchange", tests3, {once: true});
     86        history.go(-1);
     87    }
     88    function tests3() {
     89        test(function () {
     90            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), '' );
     91        }, 'replaceState must replace the existing state and not add an extra one');
     92        iframe.contentWindow.addEventListener("hashchange", tests4, {once: true});
     93        history.go(2);
     94    }
     95    function tests4() {
     96        test(function () {
     97            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test2' );
     98        }, 'replaceState must replace the existing state without altering the forward history');
     99        test(function () {
    100            assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','//exa mple'); });
    101        }, 'replaceState must not be allowed to create invalid URLs');
    102        test(function () {
    103            assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','http://www.example.com/'); });
    104        }, 'replaceState must not be allowed to create cross-origin URLs');
    105        test(function () {
    106            assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','about:blank'); });
    107        }, 'replaceState must not be allowed to create cross-origin URLs (about:blank)');
    108        test(function () {
    109            assert_throws_dom('SECURITY_ERR',function () { history.replaceState('','','data:text/html,'); });
    110        }, 'replaceState must not be allowed to create cross-origin URLs (data:URI)');
    111        test(function () {
    112            assert_throws_dom('SECURITY_ERR',iframe.contentWindow.DOMException,function () { iframe.contentWindow.history.replaceState('','','http://www.example.com/'); });
    113        }, 'security errors are expected to be thrown in the context of the document that owns the history object');
    114        test(function () {
    115            //avoids browsers running .go synchronously when only a hash change is involved
    116            iframe.contentWindow.history.replaceState('','','/testing_ignore_me_404#test4');
    117            assert_equals( iframe.contentWindow.location.pathname, '/testing_ignore_me_404' );
    118        }, 'replaceState must be able to set location.pathname');
    119        test(function () {
    120            var newURL = location.href.replace(/\/[^\/]*$/)+'/testing_ignore_me_404/';
    121            iframe.contentWindow.history.replaceState('','',newURL);
    122            assert_equals( iframe.contentWindow.location.href, newURL );
    123        }, 'replaceState must be able to set absolute URLs to the same host');
    124 
    125        //allow the browser to run the .go
    126        iframe.contentWindow.addEventListener("popstate", tests5, {once: true});
    127        //begin setup for "[must not] remove any tasks queued by the history traversal task source"
    128        iframe.contentWindow.history.go(-1); //must be queued so the next command takes place *beforehand*
    129        try {
    130            //must not remove the queued navigation in the same browsing context
    131            iframe.contentWindow.history.replaceState('','',iframe.contentWindow.location.pathname+'#test5');
    132        } catch(unsuperr2) {}
    133    }
    134    function tests5() {
    135        test(function () {
    136            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' );
    137        }, 'replaceState must not remove any tasks queued by the history traversal task source');
    138        iframe.contentWindow.addEventListener("popstate", tests6, {once: true});
    139        //Safari 5.0.3 fails here - it navigates *this* document to the *iframe's* location, instead of just navigating the iframe
    140        history.go(1);
    141    }
    142    function tests6() {
    143        test(function () {
    144            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test5' );
    145        }, '.go must queue a task with the history traversal task source (run asynchronously)');
    146        //end "[must not] remove any tasks queued by the history traversal task source"
    147        window.addEventListener('hashchange',function () { hashchng = true; },false);
    148        try {
    149            //push a state that changes the hash
    150            iframe.contentWindow.history.replaceState('','',iframe.contentWindow.location.pathname+'#test6');
    151        } catch(unsuperr) {}
    152        setTimeout(tests7,50); //allow the hashchange event to process, if the browser has mistakenly fired it
    153    }
    154    function tests7() {
    155        test(function () {
    156            assert_false( hashchng );
    157        }, 'replaceState must not fire hashchange events');
    158        test(function () {
    159            assert_throws_dom( 'DATA_CLONE_ERR', function () {
    160                history.replaceState({dummy:function () {}},'');
    161            } );
    162        }, 'replaceState must not be able to use a function as data');
    163        test(function () {
    164            assert_throws_dom( 'DATA_CLONE_ERR', function () {
    165                history.replaceState({dummy:window},'');
    166            } );
    167        }, 'replaceState must not be able to use a DOM node as data');
    168        test(function () {
    169            try { a.b = c; } catch(errdata) {
    170                history.replaceState({dummy:errdata},'');
    171                assert_equals(ReferenceError.prototype, Object.getPrototypeOf(history.state.dummy));
    172            }
    173        }, 'replaceState must be able to use an error object as data');
    174        test(function () {
    175            assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () {
    176                iframe.contentWindow.history.replaceState(document,'');
    177            });
    178        }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)');
    179        cloneobj = {
    180            nulldata: null,
    181            udefdata: window.undefined,
    182            booldata: true,
    183            numdata: 1,
    184            strdata: 'string data',
    185            boolobj: new Boolean(true),
    186            numobj: new Number(1),
    187            strobj: new String('string data'),
    188            datedata: new Date(),
    189            regdata: /a/g,
    190            arrdata: [1]
    191        };
    192        cloneobj.regdata.lastIndex = 1;
    193        cloneobj.looped = cloneobj;
    194        //test the ImageData type, if the browser supports it
    195        var canvas = document.createElement('canvas');
    196        if( canvas.getContext && ( canvas = canvas.getContext('2d') ) && canvas.createImageData ) {
    197            canvassup = true;
    198            cloneobj.imgdata = canvas.createImageData(1,1);
    199        }
    200        test(function () {
    201            try {
    202                iframe.contentWindow.history.replaceState(cloneobj,'new title');
    203            } catch(e) {
    204                cloneobj.looped = null;
    205                //try again because this object is needed for future tests
    206                iframe.contentWindow.history.replaceState(cloneobj,'new title');
    207                //rethrow so the browser gets a FAIL for not coping with the circular reference; "internal structured cloning algorithm" step 1
    208                throw(e);
    209            }
    210        }, 'replaceState must be able to make structured clones of complex objects');
    211        test(function () {
    212            assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' );
    213        }, 'history.state should also reference a clone of the original object');
    214        test(function () {
    215            assert_not_equals( cloneobj, iframe.contentWindow.history.state );
    216        }, 'history.state should be a clone of the original object, not a reference to it');
    217        iframe.contentWindow.addEventListener("popstate", tests8, {once: true});
    218        history.go(-1);
    219    }
    220    function tests8() {
    221        var eventtime = setTimeout(function () { tests9(false); },500); //should be cleared by the event handler long before it has a chance to fire
    222        iframe.contentWindow.addEventListener('popstate',function (e) { clearTimeout(eventtime); tests9(true,e); },false);
    223        history.forward();
    224    }
    225    function tests9(hasFired,ev) {
    226        test(function () {
    227            assert_true( hasFired );
    228        }, 'popstate event should fire when navigation occurs');
    229        test(function () {
    230            assert_true( !!ev && typeof(ev.state) != 'undefined', 'state information was not passed' );
    231            assert_true( !!ev.state, 'state information does not contain the expected value - browser is probably stuck in the wrong history position' );
    232            assert_equals( ev.state.nulldata, null, 'state null data was not correct' );
    233            assert_equals( ev.state.udefdata, window.undefined, 'state undefined data was not correct' );
    234            assert_true( ev.state.booldata, 'state boolean data was not correct' );
    235            assert_equals( ev.state.numdata, 1, 'state numeric data was not correct' );
    236            assert_equals( ev.state.strdata, 'string data', 'state string data was not correct' );
    237            assert_true( !!ev.state.datedata.getTime, 'state date data was not correct' );
    238            assert_own_property( ev.state, 'regdata', 'state regex data was not correct' );
    239            assert_equals( ev.state.regdata.source, 'a', 'state regex pattern data was not correct' );
    240            assert_true( ev.state.regdata.global, 'state regex flag data was not correct' );
    241            assert_equals( ev.state.regdata.lastIndex, 0, 'state regex lastIndex data was not correct' );
    242            assert_equals( ev.state.arrdata.length, 1, 'state array data was not correct' );
    243            assert_true( ev.state.boolobj.valueOf(), 'state boolean data was not correct' );
    244            assert_equals( ev.state.numobj.valueOf(), 1, 'state numeric data was not correct' );
    245            assert_equals( ev.state.strobj.valueOf(), 'string data', 'state string data was not correct' );
    246            if( canvassup ) {
    247                assert_equals( ev.state.imgdata.width, 1, 'state ImageData was not correct' );
    248            }
    249        }, 'popstate event should pass the state data');
    250        test(function () {
    251            assert_equals( ev.state.looped, ev.state );
    252        }, 'state data should cope with circular object references');
    253        test(function () {
    254            assert_not_equals( cloneobj, ev.state );
    255        }, 'state data should be a clone of the original object, not a reference to it');
    256        test(function () {
    257            assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' );
    258        }, 'history.state should also reference a clone of the original object (2)');
    259        test(function () {
    260            assert_not_equals( cloneobj, iframe.contentWindow.history.state );
    261        }, 'history.state should be a clone of the original object, not a reference to it (2)');
    262        test(function () {
    263            assert_equals( iframe.contentWindow.history.state, ev.state );
    264        }, 'history.state should be identical to the object passed to the event handler unless history.state is updated');
    265        try {
    266            iframe.contentWindow.persistval = true;
    267            iframe.contentWindow.history.replaceState('','', location.href.replace(/\/[^\/]*$/,'/blank3.html') );
    268        } catch(unsuperr) {}
    269        //it's already cached, so this should be very fast if the browser mistakenly loads it
    270        //it should not need to load at all, since it's just a pushed state
    271        setTimeout(tests10,1000);
    272    }
    273    function tests10() {
    274        test(function () {
    275            assert_true( iframe.contentWindow.persistval && !iframe.contentWindow.forreal );
    276        }, 'replaceState should not actually load the new URL');
    277        atstep = 3;
    278        iframe.contentWindow.location.reload(); //load the real URL
    279        lasttimer = setTimeout(function () { tests11(false); },3000); //should be cleared by the onload handler long before it has a chance to fire
    280    }
    281    function tests11(passed) {
    282        test(function () {
    283            assert_true( passed, 'expected a load event to fire when reloading the URL from cache, gave up waiting after 3 seconds' );
    284        }, 'reloading a replaced state should actually load the new URL');
    285        //try to make browsers behave when reloading so that the correct URL is recovered - does not always work
    286        iframe.contentWindow.location.href = location.href.replace(/\/[^\/]*$/,'/blank.html');
    287        done();
    288    }
    289 
    290    if( atstep == 1 ) {
    291        //blank2 has loaded
    292        atstep = 2;
    293        //use a timeout, because some browsers intentionally do not add history entries for URL changes in an onload thread
    294        setTimeout(tests1,100);
    295    } else if( atstep == 3 ) {
    296        //blank3 should now have loaded after the .reload() command
    297        atstep = 4;
    298        clearTimeout(lasttimer);
    299        tests11(true);
    300    }
    301 }
    302 
    303 
    304 
    305        </script>
    306    </head>
    307    <body>
    308 
    309        <noscript><p>Enable JavaScript and reload</p></noscript>
    310        <p>WARNING: This test should always be loaded in a new tab/window, to avoid browsers attempting to recover the state of frames, and history length. Do not reload the test.</p>
    311        <div id="log">Running test...</div>
    312        <p><iframe onload="reportload();" src="blank.html"></iframe></p>
    313        <p><iframe src="blank.html"></iframe></p>
    314        <p><iframe src="blank2.html"></iframe></p>
    315        <p><iframe src="blank3.html"></iframe></p>
    316 
    317    </body>
    318 </html>