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