@@ -20,6 +20,11 @@ const kHost = Symbol('host');
20
20
const kPort = Symbol ( 'port' ) ;
21
21
const kDomain = Symbol ( 'domain' ) ;
22
22
23
+ // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object
24
+ const IteratorPrototype = Object . getPrototypeOf (
25
+ Object . getPrototypeOf ( [ ] [ Symbol . iterator ] ( ) )
26
+ ) ;
27
+
23
28
function StorageObject ( ) { }
24
29
StorageObject . prototype = Object . create ( null ) ;
25
30
@@ -101,7 +106,8 @@ class URL {
101
106
this [ context ] . query = query ;
102
107
this [ context ] . fragment = fragment ;
103
108
this [ context ] . host = host ;
104
- this [ searchParams ] = new URLSearchParams ( this ) ;
109
+ this [ searchParams ] = new URLSearchParams ( query ) ;
110
+ this [ searchParams ] [ context ] = this ;
105
111
} ) ;
106
112
}
107
113
@@ -318,8 +324,31 @@ class URL {
318
324
}
319
325
320
326
set search ( search ) {
321
- update ( this , search ) ;
322
- this [ searchParams ] [ searchParams ] = querystring . parse ( this . search ) ;
327
+ search = String ( search ) ;
328
+ if ( search [ 0 ] === '?' ) search = search . slice ( 1 ) ;
329
+ if ( ! search ) {
330
+ this [ context ] . query = null ;
331
+ this [ context ] . flags &= ~ binding . URL_FLAGS_HAS_QUERY ;
332
+ this [ searchParams ] [ searchParams ] = { } ;
333
+ return ;
334
+ }
335
+ this [ context ] . query = '' ;
336
+ binding . parse ( search ,
337
+ binding . kQuery ,
338
+ null ,
339
+ this [ context ] ,
340
+ ( flags , protocol , username , password ,
341
+ host , port , path , query , fragment ) => {
342
+ if ( flags & binding . URL_FLAGS_FAILED )
343
+ return ;
344
+ if ( query ) {
345
+ this [ context ] . query = query ;
346
+ this [ context ] . flags |= binding . URL_FLAGS_HAS_QUERY ;
347
+ } else {
348
+ this [ context ] . flags &= ~ binding . URL_FLAGS_HAS_QUERY ;
349
+ }
350
+ } ) ;
351
+ this [ searchParams ] [ searchParams ] = querystring . parse ( search ) ;
323
352
}
324
353
325
354
get hash ( ) {
@@ -493,105 +522,273 @@ function encodeAuth(str) {
493
522
return out ;
494
523
}
495
524
496
- function update ( url , search ) {
497
- search = String ( search ) ;
498
- if ( ! search ) {
499
- url [ context ] . query = null ;
500
- url [ context ] . flags &= ~ binding . URL_FLAGS_HAS_QUERY ;
525
+ function update ( url , params ) {
526
+ if ( ! url )
501
527
return ;
528
+
529
+ url [ context ] . query = params . toString ( ) ;
530
+ }
531
+
532
+ function getSearchParamPairs ( target ) {
533
+ const obj = target [ searchParams ] ;
534
+ const keys = Object . keys ( obj ) ;
535
+ const values = [ ] ;
536
+ for ( var i = 0 ; i < keys . length ; i ++ ) {
537
+ const name = keys [ i ] ;
538
+ const value = obj [ name ] ;
539
+ if ( Array . isArray ( value ) ) {
540
+ for ( const item of value )
541
+ values . push ( [ name , item ] ) ;
542
+ } else {
543
+ values . push ( [ name , value ] ) ;
544
+ }
502
545
}
503
- if ( search [ 0 ] === '?' ) search = search . slice ( 1 ) ;
504
- url [ context ] . query = '' ;
505
- binding . parse ( search ,
506
- binding . kQuery ,
507
- null ,
508
- url [ context ] ,
509
- ( flags , protocol , username , password ,
510
- host , port , path , query , fragment ) => {
511
- if ( flags & binding . URL_FLAGS_FAILED )
512
- return ;
513
- if ( query ) {
514
- url [ context ] . query = query ;
515
- url [ context ] . flags |= binding . URL_FLAGS_HAS_QUERY ;
516
- } else {
517
- url [ context ] . flags &= ~ binding . URL_FLAGS_HAS_QUERY ;
518
- }
519
- } ) ;
546
+ return values ;
520
547
}
521
548
522
549
class URLSearchParams {
523
- constructor ( url ) {
524
- this [ context ] = url ;
525
- this [ searchParams ] = querystring . parse ( url [ context ] . search || '' ) ;
550
+ constructor ( init = '' ) {
551
+ if ( init instanceof URLSearchParams ) {
552
+ const childParams = init [ searchParams ] ;
553
+ this [ searchParams ] = Object . assign ( Object . create ( null ) , childParams ) ;
554
+ } else {
555
+ init = String ( init ) ;
556
+ if ( init [ 0 ] === '?' ) init = init . slice ( 1 ) ;
557
+ this [ searchParams ] = querystring . parse ( init ) ;
558
+ }
559
+
560
+ // "associated url object"
561
+ this [ context ] = null ;
562
+
563
+ // Class string for an instance of URLSearchParams. This is different from
564
+ // the class string of the prototype object (set below).
565
+ Object . defineProperty ( this , Symbol . toStringTag , {
566
+ value : 'URLSearchParams' ,
567
+ writable : false ,
568
+ enumerable : false ,
569
+ configurable : true
570
+ } ) ;
526
571
}
527
572
528
573
append ( name , value ) {
574
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
575
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
576
+ }
577
+ if ( arguments . length < 2 ) {
578
+ throw new TypeError (
579
+ 'Both `name` and `value` arguments need to be specified' ) ;
580
+ }
581
+
529
582
const obj = this [ searchParams ] ;
530
583
name = String ( name ) ;
531
584
value = String ( value ) ;
532
585
var existing = obj [ name ] ;
533
- if ( ! existing ) {
586
+ if ( existing === undefined ) {
534
587
obj [ name ] = value ;
535
588
} else if ( Array . isArray ( existing ) ) {
536
589
existing . push ( value ) ;
537
590
} else {
538
591
obj [ name ] = [ existing , value ] ;
539
592
}
540
- update ( this [ context ] , querystring . stringify ( obj ) ) ;
593
+ update ( this [ context ] , this ) ;
541
594
}
542
595
543
596
delete ( name ) {
597
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
598
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
599
+ }
600
+ if ( arguments . length < 1 ) {
601
+ throw new TypeError ( 'The `name` argument needs to be specified' ) ;
602
+ }
603
+
544
604
const obj = this [ searchParams ] ;
545
605
name = String ( name ) ;
546
606
delete obj [ name ] ;
547
- update ( this [ context ] , querystring . stringify ( obj ) ) ;
607
+ update ( this [ context ] , this ) ;
548
608
}
549
609
550
610
set ( name , value ) {
611
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
612
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
613
+ }
614
+ if ( arguments . length < 2 ) {
615
+ throw new TypeError (
616
+ 'Both `name` and `value` arguments need to be specified' ) ;
617
+ }
618
+
551
619
const obj = this [ searchParams ] ;
552
620
name = String ( name ) ;
553
621
value = String ( value ) ;
554
622
obj [ name ] = value ;
555
- update ( this [ context ] , querystring . stringify ( obj ) ) ;
623
+ update ( this [ context ] , this ) ;
556
624
}
557
625
558
626
get ( name ) {
627
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
628
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
629
+ }
630
+ if ( arguments . length < 1 ) {
631
+ throw new TypeError ( 'The `name` argument needs to be specified' ) ;
632
+ }
633
+
559
634
const obj = this [ searchParams ] ;
560
635
name = String ( name ) ;
561
636
var value = obj [ name ] ;
562
- return Array . isArray ( value ) ? value [ 0 ] : value ;
637
+ return value === undefined ? null : Array . isArray ( value ) ? value [ 0 ] : value ;
563
638
}
564
639
565
640
getAll ( name ) {
641
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
642
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
643
+ }
644
+ if ( arguments . length < 1 ) {
645
+ throw new TypeError ( 'The `name` argument needs to be specified' ) ;
646
+ }
647
+
566
648
const obj = this [ searchParams ] ;
567
649
name = String ( name ) ;
568
650
var value = obj [ name ] ;
569
651
return value === undefined ? [ ] : Array . isArray ( value ) ? value : [ value ] ;
570
652
}
571
653
572
654
has ( name ) {
655
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
656
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
657
+ }
658
+ if ( arguments . length < 1 ) {
659
+ throw new TypeError ( 'The `name` argument needs to be specified' ) ;
660
+ }
661
+
573
662
const obj = this [ searchParams ] ;
574
663
name = String ( name ) ;
575
664
return name in obj ;
576
665
}
577
666
578
- * [ Symbol . iterator ] ( ) {
579
- const obj = this [ searchParams ] ;
580
- for ( const name in obj ) {
581
- const value = obj [ name ] ;
582
- if ( Array . isArray ( value ) ) {
583
- for ( const item of value )
584
- yield [ name , item ] ;
585
- } else {
586
- yield [ name , value ] ;
587
- }
667
+ // https://heycam.github.io/webidl/#es-iterators
668
+ // Define entries here rather than [Symbol.iterator] as the function name
669
+ // must be set to `entries`.
670
+ entries ( ) {
671
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
672
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
588
673
}
674
+
675
+ return createSearchParamsIterator ( this , 'key+value' ) ;
589
676
}
590
677
678
+ forEach ( callback , thisArg = undefined ) {
679
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
680
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
681
+ }
682
+ if ( arguments . length < 1 ) {
683
+ throw new TypeError ( 'The `callback` argument needs to be specified' ) ;
684
+ }
685
+
686
+ let pairs = getSearchParamPairs ( this ) ;
687
+
688
+ var i = 0 ;
689
+ while ( i < pairs . length ) {
690
+ const [ key , value ] = pairs [ i ] ;
691
+ callback . call ( thisArg , value , key , this ) ;
692
+ pairs = getSearchParamPairs ( this ) ;
693
+ i ++ ;
694
+ }
695
+ }
696
+
697
+ // https://heycam.github.io/webidl/#es-iterable
698
+ keys ( ) {
699
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
700
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
701
+ }
702
+
703
+ return createSearchParamsIterator ( this , 'key' ) ;
704
+ }
705
+
706
+ values ( ) {
707
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
708
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
709
+ }
710
+
711
+ return createSearchParamsIterator ( this , 'value' ) ;
712
+ }
713
+
714
+ // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
591
715
toString ( ) {
716
+ if ( ! this || ! ( this instanceof URLSearchParams ) ) {
717
+ throw new TypeError ( 'Value of `this` is not a URLSearchParams' ) ;
718
+ }
719
+
592
720
return querystring . stringify ( this [ searchParams ] ) ;
593
721
}
594
722
}
723
+ // https://heycam.github.io/webidl/#es-iterable-entries
724
+ URLSearchParams . prototype [ Symbol . iterator ] = URLSearchParams . prototype . entries ;
725
+ Object . defineProperty ( URLSearchParams . prototype , Symbol . toStringTag , {
726
+ value : 'URLSearchParamsPrototype' ,
727
+ writable : false ,
728
+ enumerable : false ,
729
+ configurable : true
730
+ } ) ;
731
+
732
+ // https://heycam.github.io/webidl/#dfn-default-iterator-object
733
+ function createSearchParamsIterator ( target , kind ) {
734
+ const iterator = Object . create ( URLSearchParamsIteratorPrototype ) ;
735
+ iterator [ context ] = {
736
+ target,
737
+ kind,
738
+ index : 0
739
+ } ;
740
+ return iterator ;
741
+ }
742
+
743
+ // https://heycam.github.io/webidl/#dfn-iterator-prototype-object
744
+ const URLSearchParamsIteratorPrototype = Object . setPrototypeOf ( {
745
+ next ( ) {
746
+ if ( ! this ||
747
+ Object . getPrototypeOf ( this ) !== URLSearchParamsIteratorPrototype ) {
748
+ throw new TypeError ( 'Value of `this` is not a URLSearchParamsIterator' ) ;
749
+ }
750
+
751
+ const {
752
+ target,
753
+ kind,
754
+ index
755
+ } = this [ context ] ;
756
+ const values = getSearchParamPairs ( target ) ;
757
+ const len = values . length ;
758
+ if ( index >= len ) {
759
+ return {
760
+ value : undefined ,
761
+ done : true
762
+ } ;
763
+ }
764
+
765
+ const pair = values [ index ] ;
766
+ this [ context ] . index = index + 1 ;
767
+
768
+ let result ;
769
+ if ( kind === 'key' ) {
770
+ result = pair [ 0 ] ;
771
+ } else if ( kind === 'value' ) {
772
+ result = pair [ 1 ] ;
773
+ } else {
774
+ result = pair ;
775
+ }
776
+
777
+ return {
778
+ value : result ,
779
+ done : false
780
+ } ;
781
+ }
782
+ } , IteratorPrototype ) ;
783
+
784
+ // Unlike interface and its prototype object, both default iterator object and
785
+ // iterator prototype object of an interface have the same class string.
786
+ Object . defineProperty ( URLSearchParamsIteratorPrototype , Symbol . toStringTag , {
787
+ value : 'URLSearchParamsIterator' ,
788
+ writable : false ,
789
+ enumerable : false ,
790
+ configurable : true
791
+ } ) ;
595
792
596
793
URL . originFor = function ( url ) {
597
794
if ( ! ( url instanceof URL ) )
0 commit comments