fix: do not run dialog callback inside transaction commit (#31606)
This commit is contained in:
		
					parent
					
						
							
								1e618ef06c
							
						
					
				
			
			
				commit
				
					
						c4d35cd18c
					
				
			
		
					 4 changed files with 58 additions and 17 deletions
				
			
		|  | @ -90,6 +90,7 @@ const LINTERS = [{ | |||
|       spawnAndCheckExitCode('python', ['script/run-clang-format.py', ...filenames]); | ||||
|     } | ||||
|     const filter = [ | ||||
|       '-readability/braces', | ||||
|       '-readability/casting', | ||||
|       '-whitespace/braces', | ||||
|       '-whitespace/indent', | ||||
|  |  | |||
|  | @ -16,6 +16,9 @@ | |||
| #include "base/mac/mac_util.h" | ||||
| #include "base/mac/scoped_cftyperef.h" | ||||
| #include "base/strings/sys_string_conversions.h" | ||||
| #include "base/task/post_task.h" | ||||
| #include "content/public/browser/browser_task_traits.h" | ||||
| #include "content/public/browser/browser_thread.h" | ||||
| #include "shell/browser/native_window.h" | ||||
| #include "shell/common/gin_converters/file_path_converter.h" | ||||
| 
 | ||||
|  | @ -290,6 +293,27 @@ void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) { | |||
|   ReadDialogPathsWithBookmarks(dialog, paths, &ignored_bookmarks); | ||||
| } | ||||
| 
 | ||||
| void ResolvePromiseInNextTick(gin_helper::Promise<v8::Local<v8::Value>> promise, | ||||
|                               v8::Local<v8::Value> value) { | ||||
|   // The completionHandler runs inside a transaction commit, and we should | ||||
|   // not do any runModal inside it. However since we can not control what | ||||
|   // users will run in the microtask, we have to delay the resolution until | ||||
|   // next tick, otherwise crash like this may happen: | ||||
|   // https://github.com/electron/electron/issues/26884 | ||||
|   base::PostTask( | ||||
|       FROM_HERE, {content::BrowserThread::UI}, | ||||
|       base::BindOnce( | ||||
|           [](gin_helper::Promise<v8::Local<v8::Value>> promise, | ||||
|              v8::Global<v8::Value> global) { | ||||
|             v8::Isolate* isolate = promise.isolate(); | ||||
|             v8::Locker locker(isolate); | ||||
|             v8::HandleScope handle_scope(isolate); | ||||
|             v8::Local<v8::Value> value = global.Get(isolate); | ||||
|             promise.Resolve(value); | ||||
|           }, | ||||
|           std::move(promise), v8::Global<v8::Value>(promise.isolate(), value))); | ||||
| } | ||||
| 
 | ||||
| }  // namespace | ||||
| 
 | ||||
| bool ShowOpenDialogSync(const DialogSettings& settings, | ||||
|  | @ -320,7 +344,6 @@ void OpenDialogCompletion(int chosen, | |||
| #if defined(MAS_BUILD) | ||||
|     dict.Set("bookmarks", std::vector<std::string>()); | ||||
| #endif | ||||
|     promise.Resolve(dict); | ||||
|   } else { | ||||
|     std::vector<base::FilePath> paths; | ||||
|     dict.Set("canceled", false); | ||||
|  | @ -336,8 +359,9 @@ void OpenDialogCompletion(int chosen, | |||
|     ReadDialogPaths(dialog, &paths); | ||||
|     dict.Set("filePaths", paths); | ||||
| #endif | ||||
|     promise.Resolve(dict); | ||||
|   } | ||||
|   ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(), | ||||
|                            dict.GetHandle()); | ||||
| } | ||||
| 
 | ||||
| void ShowOpenDialog(const DialogSettings& settings, | ||||
|  | @ -410,7 +434,8 @@ void SaveDialogCompletion(int chosen, | |||
|     } | ||||
| #endif | ||||
|   } | ||||
|   promise.Resolve(dict); | ||||
|   ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(), | ||||
|                            dict.GetHandle()); | ||||
| } | ||||
| 
 | ||||
| void ShowSaveDialog(const DialogSettings& settings, | ||||
|  |  | |||
|  | @ -17,6 +17,9 @@ | |||
| #include "base/mac/scoped_nsobject.h" | ||||
| #include "base/no_destructor.h" | ||||
| #include "base/strings/sys_string_conversions.h" | ||||
| #include "base/task/post_task.h" | ||||
| #include "content/public/browser/browser_task_traits.h" | ||||
| #include "content/public/browser/browser_thread.h" | ||||
| #include "shell/browser/native_window.h" | ||||
| #include "skia/ext/skia_utils_mac.h" | ||||
| #include "ui/gfx/image/image_skia.h" | ||||
|  | @ -160,20 +163,26 @@ void ShowMessageBox(const MessageBoxSettings& settings, | |||
|     __block absl::optional<int> id = std::move(settings.id); | ||||
|     __block int cancel_id = settings.cancel_id; | ||||
| 
 | ||||
|     [alert beginSheetModalForWindow:window | ||||
|                   completionHandler:^(NSModalResponse response) { | ||||
|                     if (id) | ||||
|                       GetDialogsMap().erase(*id); | ||||
|                     // When the alert is cancelled programmatically, the | ||||
|                     // response would be something like -1000. This currently | ||||
|                     // only happens when users call CloseMessageBox API, and we | ||||
|                     // should return cancelId as result. | ||||
|                     if (response < 0) | ||||
|                       response = cancel_id; | ||||
|                     std::move(callback_).Run( | ||||
|                         response, alert.suppressionButton.state == NSOnState); | ||||
|                     [alert release]; | ||||
|                   }]; | ||||
|     auto handler = ^(NSModalResponse response) { | ||||
|       if (id) | ||||
|         GetDialogsMap().erase(*id); | ||||
|       // When the alert is cancelled programmatically, the response would be | ||||
|       // something like -1000. This currently only happens when users call | ||||
|       // CloseMessageBox API, and we should return cancelId as result. | ||||
|       if (response < 0) | ||||
|         response = cancel_id; | ||||
|       bool suppressed = alert.suppressionButton.state == NSOnState; | ||||
|       [alert release]; | ||||
|       // The completionHandler runs inside a transaction commit, and we should | ||||
|       // not do any runModal inside it. However since we can not control what | ||||
|       // users will run in the callback, we have to delay running the callback | ||||
|       // until next tick, otherwise crash like this may happen: | ||||
|       // https://github.com/electron/electron/issues/26884 | ||||
|       base::PostTask( | ||||
|           FROM_HERE, {content::BrowserThread::UI}, | ||||
|           base::BindOnce(std::move(callback_), response, suppressed)); | ||||
|     }; | ||||
|     [alert beginSheetModalForWindow:window completionHandler:handler]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -106,6 +106,12 @@ class Promise : public PromiseBase { | |||
|     return resolved.GetHandle(); | ||||
|   } | ||||
| 
 | ||||
|   // Convert to another type.
 | ||||
|   template <typename NT> | ||||
|   Promise<NT> As() { | ||||
|     return Promise<NT>(isolate(), GetInner()); | ||||
|   } | ||||
| 
 | ||||
|   // Promise resolution is a microtask
 | ||||
|   // We use the MicrotasksRunner to trigger the running of pending microtasks
 | ||||
|   v8::Maybe<bool> Resolve(const RT& value) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cheng Zhao
				Cheng Zhao