Bug 1267309 - Escape embedded speech command delimiters. r=m_kato
authorEitan Isaacson <eitan@monotonous.org>
Tue, 26 Apr 2016 11:53:42 -0700
changeset 357638 efb687e87f74d8b9b3870a21e658c95102d56f6c
parent 357637 019a39ca536e75d5d6f05313de628b7aee0b0af3
child 357639 431d60d0b211497e0ffd937d5e0f7de3b661ba49
push id16816
push userbmo:gasolin@mozilla.com
push dateFri, 29 Apr 2016 03:33:20 +0000
reviewersm_kato
bugs1267309
milestone49.0a1
Bug 1267309 - Escape embedded speech command delimiters. r=m_kato MozReview-Commit-ID: qv7TbGGWdE
dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -12,24 +12,33 @@
 #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>
 
+// 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)
+  SpeechTaskCallback(nsISpeechTask* aTask,
+                     NSSpeechSynthesizer* aSynth,
+                     const nsTArray<size_t>& aOffsets)
     : mTask(aTask)
     , mSpeechSynthesizer(aSynth)
+    , mOffsets(aOffsets)
   {
     mStartingTime = TimeStamp::Now();
   }
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
 
   NS_DECL_NSISPEECHTASKCALLBACK
@@ -45,16 +54,17 @@ private:
   }
 
   float GetTimeDurationFromStart();
 
   nsCOMPtr<nsISpeechTask> mTask;
   NSSpeechSynthesizer* mSpeechSynthesizer;
   TimeStamp mStartingTime;
   uint32_t mCurrentIndex;
+  nsTArray<size_t> mOffsets;
 };
 
 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
@@ -124,17 +134,17 @@ SpeechTaskCallback::GetTimeDurationFromS
 {
   TimeDuration duration = TimeStamp::Now() - mStartingTime;
   return duration.ToMilliseconds();
 }
 
 void
 SpeechTaskCallback::OnWillSpeakWord(uint32_t aIndex)
 {
-  mCurrentIndex = aIndex;
+  mCurrentIndex = mOffsets[aIndex];
   if (!mTask) {
     return;
   }
   mTask->DispatchBoundary(NS_LITERAL_STRING("word"),
                           GetTimeDurationFromStart(), mCurrentIndex);
 }
 
 void
@@ -387,25 +397,52 @@ OSXSpeechSynthesizerService::Speak(const
   NSNumber* defaultPitch =
     [synth objectForProperty:NSSpeechPitchBaseProperty error:nil];
   if (defaultPitch) {
     int newPitch = [defaultPitch intValue] * (aPitch / 2 + 0.5);
     [synth setObject:[NSNumber numberWithInt:newPitch]
            forProperty:NSSpeechPitchBaseProperty error:nil];
   }
 
-  RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth);
+  nsAutoString escapedText;
+  // We need to map the the offsets from the given text to the escaped text.
+  // The index of the offsets array is the position in the escaped text,
+  // the element value is the position in the user-supplied text.
+  nsTArray<size_t> offsets;
+  offsets.SetCapacity(aText.Length());
+
+  // This loop looks for occurances of "[[" or "]]", escapes them, and
+  // populates the offsets array to supply a map to the original offsets.
+  for (size_t i = 0; i < aText.Length(); i++) {
+    if (aText.Length() > i + 1 &&
+        ((aText[i] == ']' && aText[i+1] == ']') ||
+         (aText[i] == '[' && aText[i+1] == '['))) {
+      escapedText.AppendLiteral(DLIM_ESCAPE_START);
+      offsets.AppendElements(strlen(DLIM_ESCAPE_START));
+      escapedText.Append(aText[i]);
+      offsets.AppendElement(i);
+      escapedText.Append(aText[++i]);
+      offsets.AppendElement(i);
+      escapedText.AppendLiteral(DLIM_ESCAPE_END);
+      offsets.AppendElements(strlen(DLIM_ESCAPE_END));
+    } else {
+      escapedText.Append(aText[i]);
+      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(aText);
+  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;
 }