html: implement ParseWithOptions and ParseFragmentWithOptions

This commit newly introduces a type for configuring a parser
called ParseOption, and implements two functions depending on it.
Along with that, this introduces ParseOptionEnableScripting to
enable setting of the scripting flag.

Fixes golang/go#16318

Change-Id: Ie7fd7d8ce286e22e7f57182fc2ce353bce578db6
Reviewed-on: https://go-review.googlesource.com/c/net/+/174157
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/html/parse.go b/html/parse.go
index e0bfc1f..992cff2 100644
--- a/html/parse.go
+++ b/html/parse.go
@@ -2300,6 +2300,33 @@
 //
 // The input is assumed to be UTF-8 encoded.
 func Parse(r io.Reader) (*Node, error) {
+	return ParseWithOptions(r)
+}
+
+// ParseFragment parses a fragment of HTML and returns the nodes that were
+// found. If the fragment is the InnerHTML for an existing element, pass that
+// element in context.
+//
+// It has the same intricacies as Parse.
+func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
+	return ParseFragmentWithOptions(r, context)
+}
+
+// ParseOption configures a parser.
+type ParseOption func(p *parser)
+
+// ParseOptionEnableScripting configures the scripting flag.
+// https://html.spec.whatwg.org/multipage/webappapis.html#enabling-and-disabling-scripting
+//
+// By default, scripting is enabled.
+func ParseOptionEnableScripting(enable bool) ParseOption {
+	return func(p *parser) {
+		p.scripting = enable
+	}
+}
+
+// ParseWithOptions is like Parse, with options.
+func ParseWithOptions(r io.Reader, opts ...ParseOption) (*Node, error) {
 	p := &parser{
 		tokenizer: NewTokenizer(r),
 		doc: &Node{
@@ -2309,6 +2336,11 @@
 		framesetOK: true,
 		im:         initialIM,
 	}
+
+	for _, f := range opts {
+		f(p)
+	}
+
 	err := p.parse()
 	if err != nil {
 		return nil, err
@@ -2316,12 +2348,8 @@
 	return p.doc, nil
 }
 
-// ParseFragment parses a fragment of HTML and returns the nodes that were
-// found. If the fragment is the InnerHTML for an existing element, pass that
-// element in context.
-//
-// It has the same intricacies as Parse.
-func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
+// ParseFragmentWithOptions is like ParseFragment, with options.
+func ParseFragmentWithOptions(r io.Reader, context *Node, opts ...ParseOption) ([]*Node, error) {
 	contextTag := ""
 	if context != nil {
 		if context.Type != ElementNode {
@@ -2345,6 +2373,10 @@
 		context:   context,
 	}
 
+	for _, f := range opts {
+		f(p)
+	}
+
 	root := &Node{
 		Type:     ElementNode,
 		DataAtom: a.Html,
diff --git a/html/parse_test.go b/html/parse_test.go
index b49181c..b16d69a 100644
--- a/html/parse_test.go
+++ b/html/parse_test.go
@@ -228,7 +228,7 @@
 					t.Fatal(err)
 				}
 
-				err = testParseCase(text, want, context, Parse)
+				err = testParseCase(text, want, context)
 
 				if err != nil {
 					t.Errorf("%s test #%d %q, %s", tf, i, text, err)
@@ -250,22 +250,7 @@
 |       <img>
 |         src="https://golang.org/doc/gopher/doc.png"
 `
-	err := testParseCase(text, want, "", func(r io.Reader) (*Node, error) {
-		p := &parser{
-			tokenizer: NewTokenizer(r),
-			doc: &Node{
-				Type: DocumentNode,
-			},
-			scripting:  false,
-			framesetOK: true,
-			im:         initialIM,
-		}
-		err := p.parse()
-		if err != nil {
-			return nil, err
-		}
-		return p.doc, nil
-	})
+	err := testParseCase(text, want, "", ParseOptionEnableScripting(false))
 
 	if err != nil {
 		t.Errorf("test with scripting is disabled, %q, %s", text, err)
@@ -276,7 +261,7 @@
 // pass, it returns an error that explains the failure.
 // text is the HTML to be parsed, want is a dump of the correct parse tree,
 // and context is the name of the context node, if any.
-func testParseCase(text, want, context string, parseFunc func(r io.Reader) (*Node, error)) (err error) {
+func testParseCase(text, want, context string, opts ...ParseOption) (err error) {
 	defer func() {
 		if x := recover(); x != nil {
 			switch e := x.(type) {
@@ -290,7 +275,7 @@
 
 	var doc *Node
 	if context == "" {
-		doc, err = parseFunc(strings.NewReader(text))
+		doc, err = ParseWithOptions(strings.NewReader(text), opts...)
 		if err != nil {
 			return err
 		}
@@ -300,7 +285,7 @@
 			DataAtom: atom.Lookup([]byte(context)),
 			Data:     context,
 		}
-		nodes, err := ParseFragment(strings.NewReader(text), contextNode)
+		nodes, err := ParseFragmentWithOptions(strings.NewReader(text), contextNode, opts...)
 		if err != nil {
 			return err
 		}