diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 2b2461c0b48..3ba1c689206 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include namespace Web::HTML { @@ -813,138 +814,140 @@ WebIDL::ExceptionOr HTMLMediaElement::select_resource() // 3. Set the media element's delaying-the-load-event flag to true (this delays the load event). m_delaying_the_load_event.emplace(document()); - // FIXME: 4. Await a stable state, allowing the task that invoked this algorithm to continue. The synchronous section consists of all the remaining - // steps of this algorithm until the algorithm says the synchronous section has ended. (Steps in synchronous sections are marked with ⌛.) + // 4. Await a stable state, allowing the task that invoked this algorithm to continue. The synchronous section consists of all the remaining + // steps of this algorithm until the algorithm says the synchronous section has ended. (Steps in synchronous sections are marked with ⌛.) - // FIXME: 5. ⌛ If the media element's blocked-on-parser flag is false, then populate the list of pending text tracks. + queue_a_microtask(&document(), GC::create_function(realm.heap(), [this, &realm]() { + // FIXME: 5. ⌛ If the media element's blocked-on-parser flag is false, then populate the list of pending text tracks. - Optional mode; - GC::Ptr candidate; + Optional mode; + GC::Ptr candidate; - // 6. FIXME: ⌛ If the media element has an assigned media provider object, then let mode be object. + // 6. FIXME: ⌛ If the media element has an assigned media provider object, then let mode be object. - // ⌛ Otherwise, if the media element has no assigned media provider object but has a src attribute, then let mode be attribute. - if (has_attribute(HTML::AttributeNames::src)) { - mode = SelectMode::Attribute; - } - // ⌛ Otherwise, if the media element does not have an assigned media provider object and does not have a src attribute, but does have - // a source element child, then let mode be children and let candidate be the first such source element child in tree order. - else if (auto* source_element = first_child_of_type()) { - mode = SelectMode::Children; - candidate = source_element; - } - // ⌛ Otherwise the media element has no assigned media provider object and has neither a src attribute nor a source element child: - else { - // 1. ⌛ Set the networkState to NETWORK_EMPTY. - m_network_state = NetworkState::Empty; + // ⌛ Otherwise, if the media element has no assigned media provider object but has a src attribute, then let mode be attribute. + if (has_attribute(HTML::AttributeNames::src)) { + mode = SelectMode::Attribute; + } + // ⌛ Otherwise, if the media element does not have an assigned media provider object and does not have a src attribute, but does have + // a source element child, then let mode be children and let candidate be the first such source element child in tree order. + else if (auto* source_element = first_child_of_type()) { + mode = SelectMode::Children; + candidate = source_element; + } + // ⌛ Otherwise the media element has no assigned media provider object and has neither a src attribute nor a source element child: + else { + // 1. ⌛ Set the networkState to NETWORK_EMPTY. + m_network_state = NetworkState::Empty; - // 2. ⌛ Set the element's delaying-the-load-event flag to false. This stops delaying the load event. - m_delaying_the_load_event.clear(); + // 2. ⌛ Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + m_delaying_the_load_event.clear(); - // 3. End the synchronous section and return. - return {}; - } - - // 7. ⌛ Set the media element's networkState to NETWORK_LOADING. - m_network_state = NetworkState::Loading; - - // 8. ⌛ Queue a media element task given the media element to fire an event named loadstart at the media element. - queue_a_media_element_task([this] { - dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::loadstart)); - }); - - // 9. Run the appropriate steps from the following list: - switch (*mode) { - // -> If mode is object - case SelectMode::Object: - // FIXME: 1. ⌛ Set the currentSrc attribute to the empty string. - // FIXME: 2. End the synchronous section, continuing the remaining steps in parallel. - // FIXME: 3. Run the resource fetch algorithm with the assigned media provider object. If that algorithm returns without aborting this one, - // then theload failed. - // FIXME: 4. Failed with media provider: Reaching this step indicates that the media resource failed to load. Take pending play promises and queue - // a media element task given the media element to run the dedicated media source failure steps with the result. - // FIXME: 5. Wait for the task queued by the previous step to have executed. - - // 6. Return. The element won't attempt to load another resource until this algorithm is triggered again. - return {}; - - // -> If mode is attribute - case SelectMode::Attribute: { - auto failed_with_attribute = [this](auto error_message) { - IGNORE_USE_IN_ESCAPING_LAMBDA bool ran_media_element_task = false; - - // 6. Failed with attribute: Reaching this step indicates that the media resource failed to load or that the given URL could not be parsed. Take - // pending play promises and queue a media element task given the media element to run the dedicated media source failure steps with the result. - queue_a_media_element_task([this, &ran_media_element_task, error_message = move(error_message)]() mutable { - auto promises = take_pending_play_promises(); - handle_media_source_failure(promises, move(error_message)).release_value_but_fixme_should_propagate_errors(); - - ran_media_element_task = true; - }); - - // 7. Wait for the task queued by the previous step to have executed. - HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&]() { return ran_media_element_task; })); - }; - - // 1. ⌛ If the src attribute's value is the empty string, then end the synchronous section, and jump down to the failed with attribute step below. - auto source = get_attribute_value(HTML::AttributeNames::src); - if (source.is_empty()) { - failed_with_attribute("The 'src' attribute is empty"_string); - return {}; + // 3. End the synchronous section and return. + return; } - // 2. ⌛ Let urlString and urlRecord be the resulting URL string and the resulting URL record, respectively, that would have resulted from parsing - // the URL specified by the src attribute's value relative to the media element's node document when the src attribute was last changed. - auto url_record = document().parse_url(source); + // 7. ⌛ Set the media element's networkState to NETWORK_LOADING. + m_network_state = NetworkState::Loading; - // 3. ⌛ If urlString was obtained successfully, set the currentSrc attribute to urlString. - if (url_record.has_value()) - m_current_src = url_record->to_string(); + // 8. ⌛ Queue a media element task given the media element to fire an event named loadstart at the media element. + queue_a_media_element_task([this] { + dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::loadstart)); + }); - // 4. End the synchronous section, continuing the remaining steps in parallel. + // 9. Run the appropriate steps from the following list: + switch (*mode) { + // -> If mode is object + case SelectMode::Object: + // FIXME: 1. ⌛ Set the currentSrc attribute to the empty string. + // FIXME: 2. End the synchronous section, continuing the remaining steps in parallel. + // FIXME: 3. Run the resource fetch algorithm with the assigned media provider object. If that algorithm returns without aborting this one, + // then theload failed. + // FIXME: 4. Failed with media provider: Reaching this step indicates that the media resource failed to load. Take pending play promises and queue + // a media element task given the media element to run the dedicated media source failure steps with the result. + // FIXME: 5. Wait for the task queued by the previous step to have executed. - // 5. If urlRecord was obtained successfully, run the resource fetch algorithm with urlRecord. If that algorithm returns without aborting this one, - // then the load failed. - if (url_record.has_value()) { - TRY(fetch_resource(*url_record, move(failed_with_attribute))); - return {}; + // 6. Return. The element won't attempt to load another resource until this algorithm is triggered again. + return; + + // -> If mode is attribute + case SelectMode::Attribute: { + auto failed_with_attribute = [this](auto error_message) { + IGNORE_USE_IN_ESCAPING_LAMBDA bool ran_media_element_task = false; + + // 6. Failed with attribute: Reaching this step indicates that the media resource failed to load or that the given URL could not be parsed. Take + // pending play promises and queue a media element task given the media element to run the dedicated media source failure steps with the result. + queue_a_media_element_task([this, &ran_media_element_task, error_message = move(error_message)]() mutable { + auto promises = take_pending_play_promises(); + handle_media_source_failure(promises, move(error_message)).release_value_but_fixme_should_propagate_errors(); + + ran_media_element_task = true; + }); + + // 7. Wait for the task queued by the previous step to have executed. + HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&]() { return ran_media_element_task; })); + }; + + // 1. ⌛ If the src attribute's value is the empty string, then end the synchronous section, and jump down to the failed with attribute step below. + auto source = get_attribute_value(HTML::AttributeNames::src); + if (source.is_empty()) { + failed_with_attribute("The 'src' attribute is empty"_string); + return; + } + + // 2. ⌛ Let urlString and urlRecord be the resulting URL string and the resulting URL record, respectively, that would have resulted from parsing + // the URL specified by the src attribute's value relative to the media element's node document when the src attribute was last changed. + auto url_record = document().parse_url(source); + + // 3. ⌛ If urlString was obtained successfully, set the currentSrc attribute to urlString. + if (url_record.has_value()) + m_current_src = url_record->to_string(); + + // 4. End the synchronous section, continuing the remaining steps in parallel. + + // 5. If urlRecord was obtained successfully, run the resource fetch algorithm with urlRecord. If that algorithm returns without aborting this one, + // then the load failed. + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, url_record = move(url_record), failed_with_attribute = move(failed_with_attribute)]() mutable { + if (url_record.has_value()) { + fetch_resource(*url_record, move(failed_with_attribute)).release_value_but_fixme_should_propagate_errors(); + return; + } + })); + + // 8. Return. The element won't attempt to load another resource until this algorithm is triggered again. + return; } - failed_with_attribute("Failed to parse 'src' attribute as a URL"_string); + // -> Otherwise (mode is children) + case SelectMode::Children: + VERIFY(candidate); - // 8. Return. The element won't attempt to load another resource until this algorithm is triggered again. - return {}; - } + // 1. ⌛ Let pointer be a position defined by two adjacent nodes in the media element's child list, treating the start of the list (before the + // first child in the list, if any) and end of the list (after the last child in the list, if any) as nodes in their own right. One node is + // the node before pointer, and the other node is the node after pointer. Initially, let pointer be the position between the candidate node + // and the next node, if there are any, or the end of the list, if it is the last node. + // + // As nodes are inserted and removed into the media element, pointer must be updated as follows: + // + // If a new node is inserted between the two nodes that define pointer + // Let pointer be the point between the node before pointer and the new node. In other words, insertions at pointer go after pointer. + // If the node before pointer is removed + // Let pointer be the point between the node after pointer and the node before the node after pointer. In other words, pointer doesn't + // move relative to the remaining nodes. + // If the node after pointer is removed + // Let pointer be the point between the node before pointer and the node after the node before pointer. Just as with the previous case, + // pointer doesn't move relative to the remaining nodes. + // Other changes don't affect pointer. - // -> Otherwise (mode is children) - case SelectMode::Children: - VERIFY(candidate); + // NOTE: We do not bother with maintaining this pointer. We inspect the DOM tree on the fly, rather than dealing + // with the headache of auto-updating this pointer as the DOM changes. - // 1. ⌛ Let pointer be a position defined by two adjacent nodes in the media element's child list, treating the start of the list (before the - // first child in the list, if any) and end of the list (after the last child in the list, if any) as nodes in their own right. One node is - // the node before pointer, and the other node is the node after pointer. Initially, let pointer be the position between the candidate node - // and the next node, if there are any, or the end of the list, if it is the last node. - // - // As nodes are inserted and removed into the media element, pointer must be updated as follows: - // - // If a new node is inserted between the two nodes that define pointer - // Let pointer be the point between the node before pointer and the new node. In other words, insertions at pointer go after pointer. - // If the node before pointer is removed - // Let pointer be the point between the node after pointer and the node before the node after pointer. In other words, pointer doesn't - // move relative to the remaining nodes. - // If the node after pointer is removed - // Let pointer be the point between the node before pointer and the node after the node before pointer. Just as with the previous case, - // pointer doesn't move relative to the remaining nodes. - // Other changes don't affect pointer. + m_source_element_selector = realm.create(*this, *candidate); + m_source_element_selector->process_candidate().release_value_but_fixme_should_propagate_errors(); - // NOTE: We do not bother with maintaining this pointer. We inspect the DOM tree on the fly, rather than dealing - // with the headache of auto-updating this pointer as the DOM changes. - - m_source_element_selector = realm.create(*this, *candidate); - TRY(m_source_element_selector->process_candidate()); - - break; - } + break; + } + })); return {}; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.txt new file mode 100644 index 00000000000..0c3d1376f82 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.txt @@ -0,0 +1,12 @@ +Harness status: OK + +Found 6 tests + +4 Pass +2 Fail +Pass audio.error initial value +Fail audio.error after successful load +Pass audio.error after setting src to the empty string +Pass video.error initial value +Fail video.error after successful load +Pass video.error after setting src to the empty string \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.txt new file mode 100644 index 00000000000..7b62b4423db --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.txt @@ -0,0 +1,23 @@ +Harness status: OK + +Found 18 tests + +18 Pass +Pass audio.currentSrc initial value +Pass audio.currentSrc after setting src attribute "" +Pass audio.currentSrc after adding source element with src attribute "" +Pass audio.currentSrc after setting src attribute "." +Pass audio.currentSrc after adding source element with src attribute "." +Pass audio.currentSrc after setting src attribute " " +Pass audio.currentSrc after adding source element with src attribute " " +Pass audio.currentSrc after setting src attribute "data:," +Pass audio.currentSrc after adding source element with src attribute "data:," +Pass video.currentSrc initial value +Pass video.currentSrc after setting src attribute "" +Pass video.currentSrc after adding source element with src attribute "" +Pass video.currentSrc after setting src attribute "." +Pass video.currentSrc after adding source element with src attribute "." +Pass video.currentSrc after setting src attribute " " +Pass video.currentSrc after adding source element with src attribute " " +Pass video.currentSrc after setting src attribute "data:," +Pass video.currentSrc after adding source element with src attribute "data:," \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.txt new file mode 100644 index 00000000000..f7b1e7630d0 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.txt @@ -0,0 +1,7 @@ +Harness status: OK + +Found 2 tests + +2 Pass +Pass src="about:blank" does not crash. +Pass src="" does not crash. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/common/media.js b/Tests/LibWeb/Text/input/wpt-import/common/media.js new file mode 100644 index 00000000000..a5a8e957e9b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/common/media.js @@ -0,0 +1,57 @@ +/** + * Returns the URL of a supported video source based on the user agent + * @param {string} base - media URL without file extension + * @returns {string} + */ +function getVideoURI(base) +{ + var extension = '.mp4'; + + var videotag = document.createElement("video"); + + if ( videotag.canPlayType ) + { + if (videotag.canPlayType('video/webm; codecs="vp9, opus"') ) + { + extension = '.webm'; + } + } + + return base + extension; +} + +/** + * Returns the URL of a supported audio source based on the user agent + * @param {string} base - media URL without file extension + * @returns {string} + */ +function getAudioURI(base) +{ + var extension = '.mp3'; + + var audiotag = document.createElement("audio"); + + if ( audiotag.canPlayType && + audiotag.canPlayType('audio/ogg') ) + { + extension = '.oga'; + } + + return base + extension; +} + +/** + * Returns the MIME type for a media URL based on the file extension. + * @param {string} url + * @returns {string} + */ +function getMediaContentType(url) { + var extension = new URL(url, location).pathname.split(".").pop(); + var map = { + "mp4" : "video/mp4", + "webm": "video/webm", + "mp3" : "audio/mp3", + "oga" : "application/ogg", + }; + return map[extension]; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.html new file mode 100644 index 00000000000..129829c200d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/error-codes/error.html @@ -0,0 +1,40 @@ + +error + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html new file mode 100644 index 00000000000..974a57f8215 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/media-elements/location-of-the-media-resource/currentSrc.html @@ -0,0 +1,48 @@ + +currentSrc + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html new file mode 100644 index 00000000000..29dd96086f5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/embedded-content/the-video-element/video_crash_empty_src.html @@ -0,0 +1,29 @@ + + + + HTML5 Media Elements: An empty src should not crash the player. + + + + + + + + +