Bug 1406358 - Keep SpeechDelegate object until speaking is finished. r?eeejay draft
authorMakoto Kato <m_kato@ga2.so-net.ne.jp>
Thu, 12 Oct 2017 17:17:38 +0900
changeset 679001 0dbb05699795a031593899deac290572803217b4
parent 678974 2ca37e969460f5dfe50b26ba94d55a6182f70d81
child 735500 8ef468dad86c6001fea68236f23c290bcf2c5187
push id84109
push userbmo:m_kato@ga2.so-net.ne.jp
push dateThu, 12 Oct 2017 08:21:49 +0000
reviewerseeejay
bugs1406358
milestone58.0a1
Bug 1406358 - Keep SpeechDelegate object until speaking is finished. r?eeejay The delegate property of NSSpeechSynthesizer doesn't seem to add refcount from 10.13, so we have to keep SpeechDelegate object until speech is finished. MozReview-Commit-ID: EVtMOPytkjR
dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -12,71 +12,122 @@
 #include "mozilla/dom/nsSynthVoiceRegistry.h"
 #include "mozilla/dom/nsSpeechTask.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Assertions.h"
 #include "OSXSpeechSynthesizerService.h"
 
 #import <Cocoa/Cocoa.h>
 
+@class SpeechDelegate;
+
 // We can escape the default delimiters ("[[" and "]]") by temporarily
 // changing the delimiters just before they appear, and changing them back
 // just after.
 #define DLIM_ESCAPE_START "[[dlim (( ))]]"
 #define DLIM_ESCAPE_END "((dlim [[ ]]))"
 
 using namespace mozilla;
 
 class SpeechTaskCallback final : public nsISpeechTaskCallback
 {
 public:
   SpeechTaskCallback(nsISpeechTask* aTask,
                      NSSpeechSynthesizer* aSynth,
-                     const nsTArray<size_t>& aOffsets)
-    : mTask(aTask)
-    , mSpeechSynthesizer(aSynth)
-    , mOffsets(aOffsets)
-  {
-    mStartingTime = TimeStamp::Now();
-  }
+                     const nsTArray<size_t>& aOffsets);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
 
   NS_DECL_NSISPEECHTASKCALLBACK
 
   void OnWillSpeakWord(uint32_t aIndex, uint32_t aLength);
   void OnError(uint32_t aIndex);
   void OnDidFinishSpeaking();
 
 private:
-  virtual ~SpeechTaskCallback()
-  {
-    [mSpeechSynthesizer release];
-  }
+  virtual ~SpeechTaskCallback();
 
   float GetTimeDurationFromStart();
 
   nsCOMPtr<nsISpeechTask> mTask;
   NSSpeechSynthesizer* mSpeechSynthesizer;
+  SpeechDelegate* mDelegate;
   TimeStamp mStartingTime;
   uint32_t mCurrentIndex;
   nsTArray<size_t> mOffsets;
 };
 
+@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
+{
+@private
+  SpeechTaskCallback* mCallback;
+}
+
+  - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
+@end
+
+@implementation SpeechDelegate
+- (id)initWithCallback:(SpeechTaskCallback*)aCallback
+{
+  [super init];
+  mCallback = aCallback;
+  return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+            willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
+{
+  mCallback->OnWillSpeakWord(aRange.location, aRange.length);
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+        didFinishSpeaking:(BOOL)aFinishedSpeaking
+{
+  mCallback->OnDidFinishSpeaking();
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
+ didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
+                 ofString:(NSString*)aString
+                  message:(NSString*)aMessage
+{
+  mCallback->OnError(aCharacterIndex);
+}
+@end
+
 NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
   NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
 
+SpeechTaskCallback::SpeechTaskCallback(nsISpeechTask* aTask,
+                                       NSSpeechSynthesizer* aSynth,
+                                       const nsTArray<size_t>& aOffsets)
+  : mTask(aTask)
+  , mSpeechSynthesizer(aSynth)
+  , mOffsets(aOffsets)
+{
+  mDelegate = [[SpeechDelegate alloc] initWithCallback:this];
+  [mSpeechSynthesizer setDelegate:mDelegate];
+  mStartingTime = TimeStamp::Now();
+}
+
+SpeechTaskCallback::~SpeechTaskCallback()
+{
+  [mSpeechSynthesizer setDelegate:nil];
+  [mDelegate release];
+  [mSpeechSynthesizer release];
+}
+
 NS_IMETHODIMP
 SpeechTaskCallback::OnCancel()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   [mSpeechSynthesizer stopSpeaking];
   return NS_OK;
 
@@ -161,54 +212,16 @@ void
 SpeechTaskCallback::OnDidFinishSpeaking()
 {
   mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
   // no longer needed
   [mSpeechSynthesizer setDelegate:nil];
   mTask = nullptr;
 }
 
-@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
-{
-@private
-  SpeechTaskCallback* mCallback;
-}
-
-  - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
-@end
-
-@implementation SpeechDelegate
-- (id)initWithCallback:(SpeechTaskCallback*)aCallback
-{
-  [super init];
-  mCallback = aCallback;
-  return self;
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
-            willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
-{
-  mCallback->OnWillSpeakWord(aRange.location, aRange.length);
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
-        didFinishSpeaking:(BOOL)aFinishedSpeaking
-{
-  mCallback->OnDidFinishSpeaking();
-}
-
-- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
- didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
-                 ofString:(NSString*)aString
-                  message:(NSString*)aMessage
-{
-  mCallback->OnError(aCharacterIndex);
-}
-@end
-
 namespace mozilla {
 namespace dom {
 
 struct OSXVoice
 {
   OSXVoice() : mIsDefault(false)
   {
   }
@@ -424,20 +437,16 @@ OSXSpeechSynthesizerService::Speak(const
       offsets.AppendElement(i);
     }
   }
 
   RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth, offsets);
   nsresult rv = aTask->Setup(callback, 0, 0, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback];
-  [synth setDelegate:delegate];
-  [delegate release ];
-
   NSString* text = nsCocoaUtils::ToNSString(escapedText);
   BOOL success = [synth startSpeakingString:text];
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 
   aTask->DispatchStart();
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;