mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 16:52:43 +00:00 
			
		
		
		
	LibWeb: Create a shadow tree for <input type=file> elements
This creates a button to prompt users to select a file, and a label to show information about the selected file(s). Clicking either shadow element will activate the input element.
This commit is contained in:
		
							parent
							
								
									8319c7cfb8
								
							
						
					
					
						commit
						435c2c24d1
					
				
					 6 changed files with 119 additions and 3 deletions
				
			
		
							
								
								
									
										54
									
								
								Tests/LibWeb/Layout/expected/input-file.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Tests/LibWeb/Layout/expected/input-file.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| Viewport <#document> at (0,0) content-size 800x600 children: not-inline | ||||
|   BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline | ||||
|     BlockContainer <body> at (8,8) content-size 784x21 children: inline | ||||
|       frag 0 from BlockContainer start: 0, length: 0, rect: [8,8 236.65625x21] baseline: 13.296875 | ||||
|       frag 1 from TextNode start: 0, length: 1, rect: [245,8 8x17] baseline: 13.296875 | ||||
|           " " | ||||
|       frag 2 from BlockContainer start: 0, length: 0, rect: [253,8 255.34375x21] baseline: 13.296875 | ||||
|       BlockContainer <input> at (8,8) content-size 236.65625x21 inline-block [BFC] children: inline | ||||
|         frag 0 from BlockContainer start: 0, length: 0, rect: [13,10 94.375x17] baseline: 13.296875 | ||||
|         frag 1 from Label start: 0, length: 0, rect: [116,8 128.28125x17] baseline: 13.296875 | ||||
|         BlockContainer <button> at (13,10) content-size 94.375x17 inline-block [BFC] children: not-inline | ||||
|           BlockContainer <(anonymous)> at (13,10) content-size 94.375x17 flex-container(column) [FFC] children: not-inline | ||||
|             BlockContainer <(anonymous)> at (13,10) content-size 94.375x17 flex-item [BFC] children: inline | ||||
|               frag 0 from TextNode start: 0, length: 14, rect: [13,10 94.375x17] baseline: 13.296875 | ||||
|                   "Select file..." | ||||
|               TextNode <#text> | ||||
|         Label <label> at (116,8) content-size 128.28125x17 inline-block [BFC] children: inline | ||||
|           frag 0 from TextNode start: 0, length: 17, rect: [116,8 128.28125x17] baseline: 13.296875 | ||||
|               "No file selected." | ||||
|           TextNode <#text> | ||||
|       TextNode <#text> | ||||
|       BlockContainer <input> at (253,8) content-size 255.34375x21 inline-block [BFC] children: inline | ||||
|         frag 0 from BlockContainer start: 0, length: 0, rect: [258,10 103.71875x17] baseline: 13.296875 | ||||
|         frag 1 from Label start: 0, length: 0, rect: [371,8 137.625x17] baseline: 13.296875 | ||||
|         BlockContainer <button> at (258,10) content-size 103.71875x17 inline-block [BFC] children: not-inline | ||||
|           BlockContainer <(anonymous)> at (258,10) content-size 103.71875x17 flex-container(column) [FFC] children: not-inline | ||||
|             BlockContainer <(anonymous)> at (258,10) content-size 103.71875x17 flex-item [BFC] children: inline | ||||
|               frag 0 from TextNode start: 0, length: 15, rect: [258,10 103.71875x17] baseline: 13.296875 | ||||
|                   "Select files..." | ||||
|               TextNode <#text> | ||||
|         Label <label> at (371,8) content-size 137.625x17 inline-block [BFC] children: inline | ||||
|           frag 0 from TextNode start: 0, length: 18, rect: [371,8 137.625x17] baseline: 13.296875 | ||||
|               "No files selected." | ||||
|           TextNode <#text> | ||||
|       TextNode <#text> | ||||
| 
 | ||||
| ViewportPaintable (Viewport<#document>) [0,0 800x600] | ||||
|   PaintableWithLines (BlockContainer<HTML>) [0,0 800x600] | ||||
|     PaintableWithLines (BlockContainer<BODY>) [8,8 784x21] | ||||
|       PaintableWithLines (BlockContainer<INPUT>) [8,8 236.65625x21] | ||||
|         PaintableWithLines (BlockContainer<BUTTON>) [8,8 104.375x21] | ||||
|           PaintableWithLines (BlockContainer(anonymous)) [13,10 94.375x17] | ||||
|             PaintableWithLines (BlockContainer(anonymous)) [13,10 94.375x17] | ||||
|               TextPaintable (TextNode<#text>) | ||||
|         PaintableWithLines (Label<LABEL>) [112,8 132.28125x17] | ||||
|           TextPaintable (TextNode<#text>) | ||||
|       TextPaintable (TextNode<#text>) | ||||
|       PaintableWithLines (BlockContainer<INPUT>) [253,8 255.34375x21] overflow: [253,8 255.625x21] | ||||
|         PaintableWithLines (BlockContainer<BUTTON>) [253,8 113.71875x21] | ||||
|           PaintableWithLines (BlockContainer(anonymous)) [258,10 103.71875x17] | ||||
|             PaintableWithLines (BlockContainer(anonymous)) [258,10 103.71875x17] | ||||
|               TextPaintable (TextNode<#text>) | ||||
|         PaintableWithLines (Label<LABEL>) [367,8 141.625x17] | ||||
|           TextPaintable (TextNode<#text>) | ||||
							
								
								
									
										2
									
								
								Tests/LibWeb/Layout/input/input-file.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Tests/LibWeb/Layout/input/input-file.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| <input type="file" /> | ||||
| <input type="file" multiple /> | ||||
|  | @ -1,4 +1,4 @@ | |||
| pass       text: "pass" | ||||
| pass    Select file...No file selected.   text: "pass" | ||||
| hidden: "pass" | ||||
| button: "pass" | ||||
| checkbox: "pass" | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ label { | |||
| } | ||||
| 
 | ||||
| /* FIXME: This is a temporary hack until we can render a native-looking frame for these. */ | ||||
| input:not([type=submit], input[type=button], input[type=image], input[type=reset], input[type=color], input[type=checkbox], input[type=radio], input[type=range]), textarea { | ||||
| input:not([type=submit], input[type=button], input[type=image], input[type=reset], input[type=color], input[type=checkbox], input[type=file], input[type=radio], input[type=range]), textarea { | ||||
|     border: 1px solid ButtonBorder; | ||||
|     min-height: 16px; | ||||
|     width: attr(size ch, 20ch); | ||||
|  |  | |||
|  | @ -69,6 +69,8 @@ void HTMLInputElement::visit_edges(Cell::Visitor& visitor) | |||
|     visitor.visit(m_placeholder_element); | ||||
|     visitor.visit(m_placeholder_text_node); | ||||
|     visitor.visit(m_color_well_element); | ||||
|     visitor.visit(m_file_button); | ||||
|     visitor.visit(m_file_label); | ||||
|     visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group); | ||||
|     visitor.visit(m_selected_files); | ||||
|     visitor.visit(m_slider_thumb); | ||||
|  | @ -80,7 +82,7 @@ JS::GCPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS:: | |||
|     if (type_state() == TypeAttributeState::Hidden) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton || type_state() == TypeAttributeState::FileUpload) | ||||
|     if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton) | ||||
|         return heap().allocate_without_realm<Layout::ButtonBox>(document(), *this, move(style)); | ||||
| 
 | ||||
|     if (type_state() == TypeAttributeState::ImageButton) | ||||
|  | @ -600,6 +602,9 @@ void HTMLInputElement::create_shadow_tree_if_needed() | |||
|     case TypeAttributeState::Color: | ||||
|         create_color_input_shadow_tree(); | ||||
|         break; | ||||
|     case TypeAttributeState::FileUpload: | ||||
|         create_file_input_shadow_tree(); | ||||
|         break; | ||||
|     case TypeAttributeState::Range: | ||||
|         create_range_input_shadow_tree(); | ||||
|         break; | ||||
|  | @ -616,6 +621,9 @@ void HTMLInputElement::update_shadow_tree() | |||
|     case TypeAttributeState::Color: | ||||
|         update_color_well_element(); | ||||
|         break; | ||||
|     case TypeAttributeState::FileUpload: | ||||
|         update_file_input_shadow_tree(); | ||||
|         break; | ||||
|     case TypeAttributeState::Range: | ||||
|         update_slider_thumb_element(); | ||||
|         break; | ||||
|  | @ -752,6 +760,53 @@ void HTMLInputElement::update_color_well_element() | |||
|     MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value)); | ||||
| } | ||||
| 
 | ||||
| void HTMLInputElement::create_file_input_shadow_tree() | ||||
| { | ||||
|     auto& realm = this->realm(); | ||||
| 
 | ||||
|     auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm, document(), *this, Bindings::ShadowRootMode::Closed); | ||||
| 
 | ||||
|     m_file_button = DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML).release_value_but_fixme_should_propagate_errors(); | ||||
|     m_file_label = DOM::create_element(document(), HTML::TagNames::label, Namespace::HTML).release_value_but_fixme_should_propagate_errors(); | ||||
|     MUST(m_file_label->set_attribute(HTML::AttributeNames::style, "padding-left: 4px;"_string)); | ||||
| 
 | ||||
|     auto on_button_click = [this](JS::VM&) { | ||||
|         show_the_picker_if_applicable(*this); | ||||
|         return JS::js_undefined(); | ||||
|     }; | ||||
| 
 | ||||
|     auto on_button_click_function = JS::NativeFunction::create(realm, move(on_button_click), 0, "", &realm); | ||||
|     auto on_button_click_callback = realm.heap().allocate_without_realm<WebIDL::CallbackType>(on_button_click_function, Bindings::host_defined_environment_settings_object(realm)); | ||||
|     m_file_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm, on_button_click_callback)); | ||||
| 
 | ||||
|     update_file_input_shadow_tree(); | ||||
| 
 | ||||
|     MUST(shadow_root->append_child(*m_file_button)); | ||||
|     MUST(shadow_root->append_child(*m_file_label)); | ||||
| 
 | ||||
|     set_shadow_root(shadow_root); | ||||
| } | ||||
| 
 | ||||
| void HTMLInputElement::update_file_input_shadow_tree() | ||||
| { | ||||
|     if (!m_file_button || !m_file_label) | ||||
|         return; | ||||
| 
 | ||||
|     auto files_label = has_attribute(HTML::AttributeNames::multiple) ? "files"sv : "file"sv; | ||||
|     m_file_button->set_text_content(MUST(String::formatted("Select {}...", files_label))); | ||||
| 
 | ||||
|     if (m_selected_files && m_selected_files->length() > 0) { | ||||
|         if (m_selected_files->length() == 1) | ||||
|             m_file_label->set_text_content(m_selected_files->item(0)->name()); | ||||
|         else | ||||
|             m_file_label->set_text_content(MUST(String::formatted("{} files selected.", m_selected_files->length()))); | ||||
|     } else { | ||||
|         m_file_label->set_text_content(MUST(String::formatted("No {} selected.", files_label))); | ||||
|     } | ||||
| 
 | ||||
|     document().invalidate_layout(); | ||||
| } | ||||
| 
 | ||||
| void HTMLInputElement::create_range_input_shadow_tree() | ||||
| { | ||||
|     auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed); | ||||
|  |  | |||
|  | @ -226,6 +226,7 @@ private: | |||
|     void update_shadow_tree(); | ||||
|     void create_text_input_shadow_tree(); | ||||
|     void create_color_input_shadow_tree(); | ||||
|     void create_file_input_shadow_tree(); | ||||
|     void create_range_input_shadow_tree(); | ||||
|     WebIDL::ExceptionOr<void> run_input_activation_behavior(DOM::Event const&); | ||||
|     void set_checked_within_group(); | ||||
|  | @ -255,6 +256,10 @@ private: | |||
|     void update_color_well_element(); | ||||
|     JS::GCPtr<DOM::Element> m_color_well_element; | ||||
| 
 | ||||
|     void update_file_input_shadow_tree(); | ||||
|     JS::GCPtr<DOM::Element> m_file_button; | ||||
|     JS::GCPtr<DOM::Element> m_file_label; | ||||
| 
 | ||||
|     void update_slider_thumb_element(); | ||||
|     JS::GCPtr<DOM::Element> m_slider_thumb; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn