| // Copyright 2014 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/dom/html_script_element.h" |
| |
| #include <deque> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/strings/string_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/base/console_log.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/dom/document.h" |
| #include "cobalt/dom/global_stats.h" |
| #include "cobalt/dom/html_element_context.h" |
| #include "cobalt/loader/decoder.h" |
| #include "cobalt/loader/fetcher_factory.h" |
| #include "cobalt/loader/sync_loader.h" |
| #include "cobalt/loader/text_decoder.h" |
| #include "cobalt/script/global_environment.h" |
| #include "cobalt/script/script_runner.h" |
| #include "cobalt/web/csp_delegate.h" |
| #include "nb/memory_scope.h" |
| #include "url/gurl.h" |
| |
| namespace cobalt { |
| namespace dom { |
| |
| namespace { |
| |
| bool PermitAnyURL(const GURL&, bool) { return true; } |
| |
| loader::RequestMode GetRequestMode( |
| const base::Optional<std::string>& cross_origin_attribute) { |
| // https://html.spec.whatwg.org/#cors-settings-attribute |
| if (cross_origin_attribute) { |
| if (*cross_origin_attribute == "use-credentials") { |
| return loader::kCORSModeIncludeCredentials; |
| } else { |
| // The invalid value default of crossorigin is Anonymous state, leading to |
| // "same-origin" credentials mode. |
| return loader::kCORSModeSameOriginCredentials; |
| } |
| } |
| // crossorigin attribute's missing value default is No CORS state, leading to |
| // "no-cors" request mode. |
| return loader::kNoCORSMode; |
| } |
| } // namespace |
| |
| // static |
| const char HTMLScriptElement::kTagName[] = "script"; |
| |
| HTMLScriptElement::HTMLScriptElement(Document* document) |
| : HTMLElement(document, base::Token(kTagName)), |
| is_already_started_(false), |
| is_parser_inserted_(false), |
| is_ready_(false), |
| load_option_(0), |
| inline_script_location_(GetSourceLocationName(), 1, 1), |
| should_execute_(true), |
| synchronous_loader_interrupt_( |
| document->html_element_context()->synchronous_loader_interrupt()) { |
| DCHECK(document->html_element_context()->script_runner()); |
| } |
| |
| std::string HTMLScriptElement::src() const { |
| auto src = GetAttribute("src"); |
| if (!src.has_value()) { |
| return ""; |
| } |
| if (!node_document()) { |
| return src.value(); |
| } |
| const GURL& base_url = node_document()->location()->url(); |
| return base_url.Resolve(src.value()).spec(); |
| } |
| |
| base::Optional<std::string> HTMLScriptElement::cross_origin() const { |
| base::Optional<std::string> cross_origin_attribute = |
| GetAttribute("crossOrigin"); |
| if (cross_origin_attribute && |
| (*cross_origin_attribute != "anonymous" && |
| *cross_origin_attribute != "use-credentials")) { |
| return std::string(); |
| } |
| return cross_origin_attribute; |
| } |
| |
| void HTMLScriptElement::set_cross_origin( |
| const base::Optional<std::string>& value) { |
| if (value) { |
| SetAttribute("crossOrigin", *value); |
| } else { |
| RemoveAttribute("crossOrigin"); |
| } |
| } |
| |
| void HTMLScriptElement::OnInsertedIntoDocument() { |
| HTMLElement::OnInsertedIntoDocument(); |
| if (!is_parser_inserted_) { |
| Prepare(); |
| } |
| } |
| |
| void HTMLScriptElement::OnParserStartTag( |
| const base::SourceLocation& opening_tag_location) { |
| inline_script_location_ = opening_tag_location; |
| ++inline_script_location_.column_number; // JavaScript code starts after ">". |
| is_parser_inserted_ = true; |
| } |
| |
| void HTMLScriptElement::OnParserEndTag() { Prepare(); } |
| |
| scoped_refptr<HTMLScriptElement> HTMLScriptElement::AsHTMLScriptElement() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return this; |
| } |
| |
| scoped_refptr<Node> HTMLScriptElement::Duplicate() const { |
| // The cloning steps for script elements must set the "already started" flag |
| // on the copy if it is set on the element being cloned. |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#already-started |
| scoped_refptr<HTMLScriptElement> new_script = HTMLElement::Duplicate() |
| ->AsElement() |
| ->AsHTMLElement() |
| ->AsHTMLScriptElement(); |
| new_script->is_already_started_ = is_already_started_; |
| return new_script; |
| } |
| |
| HTMLScriptElement::~HTMLScriptElement() { |
| // Remove the script from the list of scripts that will execute in order as |
| // soon as possible associated with the Document, only if the document still |
| // exists when the script element is destroyed. |
| // NOTE: While the garbage collection prevention logic will typically protect |
| // from this, it is still possible during shutdown. |
| if (document_) { |
| std::deque<HTMLScriptElement*>* scripts_to_be_executed = |
| document_->scripts_to_be_executed(); |
| |
| std::deque<HTMLScriptElement*>::iterator it = std::find( |
| scripts_to_be_executed->begin(), scripts_to_be_executed->end(), this); |
| if (it != scripts_to_be_executed->end()) { |
| scripts_to_be_executed->erase(it); |
| } |
| ExecuteSyncScripts(); |
| } |
| } |
| |
| // Algorithm for Prepare: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#prepare-a-script |
| void HTMLScriptElement::Prepare() { |
| TRACK_MEMORY_SCOPE("DOM"); |
| // Custom, not in any spec. |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(base::MessageLoop::current()); |
| DCHECK(!loader_ || is_already_started_); |
| TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::Prepare()"); |
| |
| error_.reset(); |
| |
| // If the script element is marked as having "already started", then the |
| // user agent must abort these steps at this point. The script is not |
| // executed. |
| if (is_already_started_) { |
| return; |
| } |
| |
| // 2. 3. 4. Not needed by Cobalt. |
| |
| // 5. If the element is not in a Document, then the user agent must abort |
| // these steps at this point. The script is not executed. |
| if (!node_document()) { |
| return; |
| } |
| document_ = base::AsWeakPtr<Document>(node_document()); |
| |
| // 6. If either: |
| // the script element has a type attribute and its value is the empty |
| // string, or |
| // the script element has no type attribute but it has a language attribute |
| // and that attribute's value is the empty string, or |
| // the script element has neither a type attribute nor a language |
| // attribute, then |
| // ...let the script block's type for this script element be |
| // "text/javascript". |
| // Otherwise, if the script element has a type attribute, let the script |
| // block's type for this script element be the value of that attribute with |
| // any leading or trailing sequences of space characters removed. |
| // Otherwise, the element has a non-empty language attribute; let the script |
| // block's type for this script element be the concatenation of the string |
| // "text/" followed by the value of the language attribute. |
| if (type() == "") { |
| set_type("text/javascript"); |
| } else { |
| std::string trimmed_type; |
| TrimWhitespaceASCII(type(), base::TRIM_ALL, &trimmed_type); |
| set_type(trimmed_type); |
| } |
| |
| // 7. If the user agent does not support the scripting language given by the |
| // script block's type for this script element, then the user agent must abort |
| // these steps at this point. The script is not executed. |
| if (type() != "text/javascript") return; |
| |
| // 8. Not needed by Cobalt. |
| |
| // 9. The user agent must set the element's "already started" flag. |
| is_already_started_ = true; |
| |
| // 10. ~ 13. Not needed by Cobalt. |
| |
| // 14. If the element has a src content attribute, run these substeps: |
| // 1. Let src be the value of the element's src attribute. |
| // 2. If src is the empty string, queue a task to fire a simple event |
| // named error at the element, and abort these steps. |
| // |
| // Need to use the "src" attribute. The |src| property is fully resolved. See |
| // header file for details. |
| auto src = GetAttribute("src").value_or(""); |
| if (HasAttribute("src") && src == "") { |
| LOG(ERROR) << "src attribute of script element is empty."; |
| |
| PreventGarbageCollectionAndPostToDispatchEvent( |
| FROM_HERE, base::Tokens::error(), |
| &prevent_gc_until_error_event_dispatch_); |
| return; |
| } |
| |
| // 3. Resolve src relative to the element. |
| // 4. If the previous step failed, queue a task to fire a simple event named |
| // error at the element, and abort these steps. |
| const GURL& base_url = document_->location()->url(); |
| url_ = base_url.Resolve(src); |
| if (!url_.is_valid()) { |
| LOG(ERROR) << src << " cannot be resolved based on " << base_url << "."; |
| |
| PreventGarbageCollectionAndPostToDispatchEvent( |
| FROM_HERE, base::Tokens::error(), |
| &prevent_gc_until_error_event_dispatch_); |
| return; |
| } |
| |
| // 5. Do a potentially CORS-enabled fetch of the resulting absolute URL, |
| // with the mode being the current state of the element's crossorigin |
| // content attribute, the origin being the origin of the script element's |
| // Document, and the default origin behaviour set to taint. |
| |
| // 15. Then, the first of the following options that describes the situation |
| // must be followed: |
| |
| // Option 1 and Option 3 are not needed by Cobalt. |
| if (HasAttribute("src") && is_parser_inserted_ && !async()) { |
| // Option 2 |
| // If the element has a src attribute, and the element has been flagged as |
| // "parser-inserted", and the element does not have an async attribute. |
| |
| if (owner_document() |
| ->html_element_context() |
| ->enable_inline_script_warnings()) { |
| CLOG(WARNING, debugger_hooks()) |
| << "A request to synchronously load a script is being made as " |
| "a result of a non-async <script> tag inlined within HTML. " |
| "You should avoid this in Cobalt because if the app is " |
| "suspended while loading the script, Cobalt's current " |
| "logic is to abort the load and without any retries or " |
| "signals. To avoid difficult to diagnose suspend/resume " |
| "bugs, it is recommended to use JavaScript to create a " |
| "script element and load it async. The <script> reference " |
| "appears at: \"" |
| << inline_script_location_ << "\" and its src is \"" << src << "\""; |
| } |
| |
| load_option_ = 2; |
| } else if (HasAttribute("src") && !async()) { |
| // Option 4 |
| // If the element has a src attribute, does not have an async attribute, and |
| // does not have the "force-async" flag set. |
| load_option_ = 4; |
| } else if (HasAttribute("src")) { |
| // Option 5 |
| // If the element has a src attribute. |
| load_option_ = 5; |
| } else { |
| // Option 6 |
| // Otherwise. |
| load_option_ = 6; |
| } |
| |
| // https://www.w3.org/TR/CSP2/#directive-script-src |
| |
| web::CspDelegate* csp_delegate = document_->csp_delegate(); |
| // If the script element has a valid nonce, we always permit it, regardless |
| // of its URL or inline nature. |
| const bool bypass_csp = |
| csp_delegate->IsValidNonce(web::CspDelegate::kScript, nonce()); |
| |
| csp::SecurityCallback csp_callback; |
| if (bypass_csp) { |
| csp_callback = base::Bind(&PermitAnyURL); |
| } else { |
| csp_callback = |
| base::Bind(&web::CspDelegate::CanLoad, base::Unretained(csp_delegate), |
| web::CspDelegate::kScript); |
| } |
| |
| // Clear fetched resource's origin before start. |
| fetched_last_url_origin_ = loader::Origin(); |
| |
| // Determine request mode from crossorigin attribute. |
| request_mode_ = GetRequestMode(GetAttribute("crossOrigin")); |
| |
| switch (load_option_) { |
| case 2: { |
| // If the element has a src attribute, and the element has been flagged as |
| // "parser-inserted", and the element does not have an async attribute. |
| |
| // The element is the pending parsing-blocking script of the Document of |
| // the parser that created the element. (There can only be one such script |
| // per Document at a time.) |
| |
| // This variable will be set to true in the completion callback for the |
| // loader below. If that completion callback never fires, the variable |
| // will stay false. This can happen if the loader was interrupted, or |
| // failed for another reason. |
| loader::LoadSynchronously( |
| html_element_context()->sync_load_thread()->message_loop(), |
| synchronous_loader_interrupt_, |
| base::Bind( |
| &loader::FetcherFactory::CreateSecureFetcher, |
| base::Unretained(html_element_context()->fetcher_factory()), url_, |
| csp_callback, request_mode_, |
| document_->location() ? document_->location()->GetOriginAsObject() |
| : loader::Origin(), |
| disk_cache::kUncompiledScript), |
| base::Bind(&loader::TextDecoder::Create, |
| base::Bind(&HTMLScriptElement::OnSyncContentProduced, |
| base::Unretained(this)), |
| loader::Decoder::OnCompleteFunction()), |
| base::Bind(&HTMLScriptElement::OnSyncLoadingComplete, |
| base::Unretained(this))); |
| |
| // This block exists to ensure that garbage collection is prevented while |
| // executing the script. |
| { |
| script::GlobalEnvironment::ScopedPreventGarbageCollection |
| scoped_prevent_gc( |
| html_element_context()->script_runner()->GetGlobalEnvironment(), |
| this); |
| ExecuteExternal(); |
| } |
| } break; |
| case 4: { |
| // This is an asynchronous script. Prevent garbage collection until |
| // loading completes and the script potentially executes. |
| PreventGCUntilLoadComplete(); |
| |
| // If the element has a src attribute, does not have an async attribute, |
| // and does not have the "force-async" flag set. |
| |
| // The element must be added to the end of the list of scripts that will |
| // execute in order as soon as possible associated with the Document of |
| // the script element at the time the prepare a script algorithm started. |
| std::deque<HTMLScriptElement*>* scripts_to_be_executed = |
| document_->scripts_to_be_executed(); |
| scripts_to_be_executed->push_back(this); |
| |
| // Fetching an external script must delay the load event |
| // of the element's document until the task that is |
| // queued by the networking task source once the resource |
| // has been fetched (defined above) has been run. |
| document_->IncreaseLoadingCounter(); |
| |
| loader::Origin origin = document_->location() |
| ? document_->location()->GetOriginAsObject() |
| : loader::Origin(); |
| |
| loader_ = html_element_context()->loader_factory()->CreateScriptLoader( |
| url_, origin, csp_callback, |
| base::Bind(&HTMLScriptElement::OnContentProduced, |
| base::Unretained(this)), |
| base::Bind(&HTMLScriptElement::OnLoadingComplete, |
| base::Unretained(this))); |
| } break; |
| case 5: { |
| // This is an asynchronous script. Prevent garbage collection until |
| // loading completes and the script potentially executes. |
| PreventGCUntilLoadComplete(); |
| |
| // If the element has a src attribute. |
| |
| // Fetching an external script must delay the load event of the element's |
| // document until the task that is queued by the networking task source |
| // once the resource has been fetched (defined above) has been run. |
| document_->IncreaseLoadingCounter(); |
| |
| // The element must be added to the set of scripts that will execute as |
| // soon as possible of the Document of the script element at the time the |
| // prepare a script algorithm started. |
| DCHECK(!loader_); |
| |
| loader::Origin origin = document_->location() |
| ? document_->location()->GetOriginAsObject() |
| : loader::Origin(); |
| |
| loader_ = html_element_context()->loader_factory()->CreateScriptLoader( |
| url_, origin, csp_callback, |
| base::Bind(&HTMLScriptElement::OnContentProduced, |
| base::Unretained(this)), |
| base::Bind(&HTMLScriptElement::OnLoadingComplete, |
| base::Unretained(this))); |
| } break; |
| case 6: { |
| // Otherwise. |
| |
| // The user agent must immediately execute the script block, even if other |
| // scripts are already executing. |
| base::Optional<std::string> content = text_content(); |
| const std::string& text = content.value_or(base::EmptyString()); |
| if (bypass_csp || text.empty() || |
| csp_delegate->AllowInline(web::CspDelegate::kScript, |
| inline_script_location_, text)) { |
| fetched_last_url_origin_ = document_->location()->GetOriginAsObject(); |
| ExecuteInternal(); |
| } else { |
| LOG(ERROR) << " empty synchronous script."; |
| PreventGarbageCollectionAndPostToDispatchEvent( |
| FROM_HERE, base::Tokens::error(), |
| &prevent_gc_until_error_event_dispatch_); |
| } |
| } break; |
| default: { NOTREACHED(); } |
| } |
| } |
| |
| void HTMLScriptElement::OnSyncContentProduced( |
| const loader::Origin& last_url_origin, |
| std::unique_ptr<std::string> content) { |
| TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::OnSyncContentProduced()"); |
| fetched_last_url_origin_ = last_url_origin; |
| content_ = std::move(content); |
| } |
| |
| void HTMLScriptElement::OnSyncLoadingComplete( |
| const base::Optional<std::string>& error) { |
| error_ = error; |
| if (!error) return; |
| TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::OnSyncLoadingComplete()"); |
| LOG(ERROR) << "Error during synchronous script load referenced from \"" |
| << inline_script_location_ << "\" : " << *error; |
| } |
| |
| // Algorithm for OnContentProduced: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#prepare-a-script |
| void HTMLScriptElement::OnContentProduced( |
| const loader::Origin& last_url_origin, |
| std::unique_ptr<std::string> content) { |
| // From Algorithm for OnContentProduced: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#prepare-a-script |
| // 15. Then, the first of the following options that describes the situation |
| // must be followed: |
| // |
| DCHECK(content); |
| TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::OnContentProduced()"); |
| |
| fetched_last_url_origin_ = last_url_origin; |
| content_ = std::move(content); |
| |
| OnReadyToExecute(); |
| } |
| |
| // Algorithm for OnLoadingComplete: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#prepare-a-script |
| void HTMLScriptElement::OnLoadingComplete( |
| const base::Optional<std::string>& error) { |
| error_ = error; |
| // GetLoadTimingInfo and create resource timing before loader released. |
| GetLoadTimingInfoAndCreateResourceTiming(); |
| |
| TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::OnLoadingComplete()"); |
| |
| if (!error_) return; |
| |
| OnReadyToExecute(); |
| } |
| |
| void HTMLScriptElement::ExecuteSyncScripts() { |
| // From Algorithm for OnContentProduced: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#prepare-a-script |
| DCHECK(document_); |
| |
| std::deque<HTMLScriptElement*>* scripts_to_be_executed = |
| document_->scripts_to_be_executed(); |
| if (scripts_to_be_executed->empty() || |
| scripts_to_be_executed->front() != this) { |
| return; |
| } |
| |
| while (true) { |
| // 2. Execution: Execute the script block corresponding to the first |
| // script element in this list of scripts that will execute in order as |
| // soon as possible. |
| HTMLScriptElement* script = scripts_to_be_executed->front(); |
| script->ExecuteExternal(); |
| |
| // NOTE: Must disable warning 6011 on Windows. It mysteriously believes |
| // that a NULL pointer is being dereferenced with this comparison. |
| MSVC_PUSH_DISABLE_WARNING(6011); |
| // If this script isn't the current object, then allow it to be garbage |
| // collected now that it has executed. |
| if (script != this) { |
| script->AllowGCAfterLoadComplete(); |
| } |
| MSVC_POP_WARNING(); |
| |
| // 3. Remove the first element from this list of scripts that will |
| // execute in order as soon as possible. |
| scripts_to_be_executed->pop_front(); |
| |
| // Fetching an external script must delay the load event of the |
| // element's document until the task that is queued by the networking |
| // task source once the resource has been fetched (defined above) |
| // has been run. |
| document_->DecreaseLoadingCounterAndMaybeDispatchLoadEvent(); |
| |
| // 4. If this list of scripts that will execute in order as soon as |
| // possible is still not empty and the first entry has already been |
| // marked |
| // as ready, then jump back to the step labeled execution. |
| if (scripts_to_be_executed->empty() || |
| !scripts_to_be_executed->front()->is_ready_) { |
| break; |
| } |
| } |
| |
| // Allow garbage collection on the current script object now that it has |
| // finished executing both itself and other pending scripts. |
| AllowGCAfterLoadComplete(); |
| } |
| |
| void HTMLScriptElement::OnReadyToExecute() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(load_option_ == 4 || load_option_ == 5); |
| if (!document_) { |
| error_ = "No document"; |
| AllowGCAfterLoadComplete(); |
| return; |
| } |
| |
| switch (load_option_) { |
| case 4: { |
| // From Algorithm for OnContentProduced: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#script-processing-src-sync |
| |
| // If the element has a src attribute, does not have an async attribute, |
| // and does not have the "force-async" flag set. |
| |
| // The task that the networking task source places on the task queue once |
| // the fetching algorithm has completed must run the following steps: |
| // 1. If the element is not now the first element in the list of scripts |
| // that will execute in order as soon as possible to which it was added |
| // above, then mark the element as ready but abort these steps without |
| // executing the script yet. |
| if (document_->scripts_to_be_executed()->front() != this) { |
| is_ready_ = true; |
| } else { |
| ExecuteSyncScripts(); |
| } |
| } break; |
| case 5: { |
| // From Algorithm for OnContentProduced: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#script-processing-src |
| |
| // If the element has a src attribute. |
| |
| // The task that the networking task source places on the task queue once |
| // the fetching algorithm has completed must execute the script block and |
| // then remove the element from the set of scripts that will execute as |
| // soon as possible. |
| ExecuteExternal(); |
| |
| // Allow garbage collection on the current object now that it has finished |
| // executing. |
| AllowGCAfterLoadComplete(); |
| |
| // Fetching an external script must delay the load event of the element's |
| // document until the task that is queued by the networking task source |
| // once the resource has been fetched (defined above) has been run. |
| document_->DecreaseLoadingCounterAndMaybeDispatchLoadEvent(); |
| } break; |
| } |
| |
| // Post a task to release the loader. |
| base::MessageLoop::current()->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&HTMLScriptElement::ReleaseLoader, this)); |
| } |
| |
| void HTMLScriptElement::ExecuteExternal() { |
| if (error_) { |
| LOG(ERROR) << *error_; |
| // If the load resulted in an error (for example a DNS error, or an HTTP |
| // 404 error) |
| // Executing the script block must just consist of firing a simple event |
| // named error at the element. |
| DispatchEvent(new web::Event(base::Tokens::error())); |
| } else { |
| DCHECK(content_); |
| Execute(*content_, base::SourceLocation(url_.spec(), 1, 1), true); |
| } |
| |
| // Release the content string now that we're finished with it. |
| content_.reset(); |
| } |
| |
| void HTMLScriptElement::ExecuteInternal() { |
| DCHECK(!error_); |
| Execute(text_content().value(), inline_script_location_, false); |
| } |
| |
| // Algorithm for Execute: |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#execute-the-script-block |
| void HTMLScriptElement::Execute(const std::string& content, |
| const base::SourceLocation& script_location, |
| bool is_external) { |
| TRACK_MEMORY_SCOPE("DOM"); |
| // If should_execute_ is set to false for whatever reason, then we |
| // immediately exit. |
| // When inserted using the document.write() method, script elements execute |
| // (typically synchronously), but when inserted using innerHTML and |
| // outerHTML attributes, they do not execute at all. |
| // https://www.w3.org/TR/2018/SPSD-html5-20180327/scripting-1.html#the-script-element. |
| if (!should_execute_) { |
| return; |
| } |
| |
| TRACE_EVENT2("cobalt::dom", "HTMLScriptElement::Execute()", "file_path", |
| script_location.file_path, "line_number", |
| script_location.line_number); |
| |
| // Since error is already handled, it is guaranteed the load is successful. |
| DCHECK(!error_); |
| |
| // The script is now being run. Track it in the global stats. |
| GlobalStats::GetInstance()->StartJavaScriptEvent(); |
| |
| // For |currentScript| logic, see |
| // https://html.spec.whatwg.org/commit-snapshots/20a0ddc6841176adab93efefb76b23e0a1e6fa43/#execute-the-script-block |
| scoped_refptr<HTMLScriptElement> old_current_script = |
| node_document()->current_script(); |
| node_document()->set_current_script(this); |
| |
| // 1. 2. 3. Not needed by Cobalt. |
| |
| // 4. Create a script, using the script block's source, the URL from which the |
| // script was obtained, the script block's type as the scripting language, and |
| // the script settings object of the script element's Document's Window |
| // object. |
| bool mute_errors = |
| request_mode_ == loader::kNoCORSMode && |
| fetched_last_url_origin_ != document_->location()->GetOriginAsObject(); |
| html_element_context()->script_runner()->Execute( |
| content, script_location, mute_errors, NULL /*out_succeeded*/); |
| |
| // 5. 6. Not needed by Cobalt. |
| |
| // 7. If the script is from an external file, fire a simple event named load |
| // at the script element. |
| // Otherwise, the script is internal; queue a task to fire a simple event |
| // named load at the script element. |
| // TODO: Remove the firing of readystatechange once we support Promise. |
| if (is_external) { |
| DispatchEvent(new web::Event(base::Tokens::load())); |
| DispatchEvent(new web::Event(base::Tokens::readystatechange())); |
| } else { |
| PreventGarbageCollectionAndPostToDispatchEvent( |
| FROM_HERE, base::Tokens::load(), |
| &prevent_gc_until_load_event_dispatch_); |
| PreventGarbageCollectionAndPostToDispatchEvent( |
| FROM_HERE, base::Tokens::readystatechange(), |
| &prevent_gc_until_ready_event_dispatch_); |
| } |
| |
| // For |currentScript| logic, see |
| // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block. |
| node_document()->set_current_script(old_current_script); |
| |
| // The script is done running. Stop tracking it in the global stats. |
| GlobalStats::GetInstance()->StopJavaScriptEvent(); |
| |
| // Notify the DomStatTracker of the execution. |
| dom_stat_tracker_->OnHtmlScriptElementExecuted(content.size()); |
| } |
| |
| void HTMLScriptElement::PreventGarbageCollectionAndPostToDispatchEvent( |
| const base::Location& location, const base::Token& token, |
| std::unique_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>* |
| scoped_prevent_gc) { |
| // Ensure that this HTMLScriptElement is not garbage collected until the event |
| // has been processed. |
| DCHECK(!(*scoped_prevent_gc)); |
| scoped_prevent_gc->reset( |
| new script::GlobalEnvironment::ScopedPreventGarbageCollection( |
| html_element_context()->script_runner()->GetGlobalEnvironment(), |
| this)); |
| PostToDispatchEventNameAndRunCallback( |
| location, token, |
| base::Bind(&HTMLScriptElement::AllowGCAfterEventDispatch, this, |
| scoped_prevent_gc)); |
| } |
| |
| void HTMLScriptElement::AllowGCAfterEventDispatch( |
| std::unique_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>* |
| scoped_prevent_gc) { |
| scoped_prevent_gc->reset(); |
| } |
| |
| void HTMLScriptElement::PreventGCUntilLoadComplete() { |
| DCHECK(!prevent_gc_until_load_complete_); |
| prevent_gc_until_load_complete_.reset( |
| new script::GlobalEnvironment::ScopedPreventGarbageCollection( |
| html_element_context()->script_runner()->GetGlobalEnvironment(), |
| this)); |
| } |
| |
| void HTMLScriptElement::AllowGCAfterLoadComplete() { |
| prevent_gc_until_load_complete_.reset(); |
| } |
| |
| void HTMLScriptElement::ReleaseLoader() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(loader_); |
| loader_.reset(); |
| } |
| |
| void HTMLScriptElement::GetLoadTimingInfoAndCreateResourceTiming() { |
| if (html_element_context()->performance() == nullptr) return; |
| if (loader_) { |
| html_element_context()->performance()->CreatePerformanceResourceTiming( |
| loader_->get_load_timing_info(), kTagName, url_.spec()); |
| } |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |