author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Fri, 15 May 2015 17:39:23 +0200 | |
changeset 244040 | 1a8343f8ed8336cbba1b236ab9725012c6c73179 |
parent 244039 | c0e709a5baca045cadd9f3bd39441cf79ab9aee8 (current diff) |
parent 243989 | 17042d4111bb1079e6a214bc4e245b210bb1fc29 (diff) |
child 244041 | 5943d32f35155feb6144f5b06d7e413888d9072e |
child 244105 | e0eae4ec22fab6f2c9b5dfcb6524d8dc07a24611 |
child 244112 | e0db8169a12397230057d7cd5695148f6dfdf169 |
push id | 59820 |
push user | cbook@mozilla.com |
push date | Fri, 15 May 2015 15:41:47 +0000 |
treeherder | mozilla-inbound@5943d32f3515 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 41.0a1 |
first release with | nightly linux32
1a8343f8ed83
/
41.0a1
/
20150515084717
/
files
nightly linux64
1a8343f8ed83
/
41.0a1
/
20150515084717
/
files
nightly mac
1a8343f8ed83
/
41.0a1
/
20150515084717
/
files
nightly win32
1a8343f8ed83
/
41.0a1
/
20150515084717
/
files
nightly win64
1a8343f8ed83
/
41.0a1
/
20150515084717
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
41.0a1
/
20150515084717
/
pushlog to previous
nightly linux64
41.0a1
/
20150515084717
/
pushlog to previous
nightly mac
41.0a1
/
20150515084717
/
pushlog to previous
nightly win32
41.0a1
/
20150515084717
/
pushlog to previous
nightly win64
41.0a1
/
20150515084717
/
pushlog to previous
|
dom/media/webspeech/synth/ipc/test/file_ipc.html | file | annotate | diff | comparison | revisions | |
dom/media/webspeech/synth/ipc/test/mochitest.ini | file | annotate | diff | comparison | revisions | |
dom/media/webspeech/synth/ipc/test/test_ipc.html | file | annotate | diff | comparison | revisions | |
layout/base/nsPresShell.cpp | file | annotate | diff | comparison | revisions | |
modules/libpref/init/all.js | file | annotate | diff | comparison | revisions |
--- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -3098,23 +3098,16 @@ Element::AttrValueToCORSMode(const nsAtt } return CORSMode(aValue->GetEnumValue()); } static const char* GetFullScreenError(nsIDocument* aDoc) { - // Block fullscreen requests in the chrome document when the fullscreen API - // is configured for content only. - if (nsContentUtils::IsFullscreenApiContentOnly() && - nsContentUtils::IsChromeDoc(aDoc)) { - return "FullScreenDeniedContentOnly"; - } - nsCOMPtr<nsPIDOMWindow> win = aDoc->GetWindow(); if (aDoc->NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED) { // Request is in a web app and in the same origin as the web app. // Don't enforce as strict security checks for web apps, the user // is supposed to have trust in them. However documents cross-origin // to the web app must still confirm to the normal security checks. return nullptr; }
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -242,17 +242,16 @@ nsString* nsContentUtils::sControlText = nsString* nsContentUtils::sMetaText = nullptr; nsString* nsContentUtils::sOSText = nullptr; nsString* nsContentUtils::sAltText = nullptr; nsString* nsContentUtils::sModifierSeparator = nullptr; bool nsContentUtils::sInitialized = false; bool nsContentUtils::sIsFullScreenApiEnabled = false; bool nsContentUtils::sTrustedFullScreenOnly = true; -bool nsContentUtils::sFullscreenApiIsContentOnly = false; bool nsContentUtils::sIsPerformanceTimingEnabled = false; bool nsContentUtils::sIsResourceTimingEnabled = false; bool nsContentUtils::sIsUserTimingLoggingEnabled = false; bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; @@ -507,22 +506,16 @@ nsContentUtils::Init() sBlockedScriptRunners = new nsTArray< nsCOMPtr<nsIRunnable> >; Preferences::AddBoolVarCache(&sAllowXULXBL_for_file, "dom.allow_XUL_XBL_for_file"); Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled, "full-screen-api.enabled"); - // Note: We deliberately read this pref here because this code runs - // before the profile loads, so users' changes to this pref in about:config - // won't have any effect on behaviour. We don't really want users messing - // with this pref, as it affects the security model of the fullscreen API. - sFullscreenApiIsContentOnly = Preferences::GetBool("full-screen-api.content-only", false); - Preferences::AddBoolVarCache(&sTrustedFullScreenOnly, "full-screen-api.allow-trusted-requests-only"); Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled, "dom.enable_performance", true); Preferences::AddBoolVarCache(&sIsResourceTimingEnabled, "dom.enable_resource_timing", true); @@ -6632,23 +6625,16 @@ nsContentUtils::IsRequestFullScreenAllow { return !sTrustedFullScreenOnly || EventStateManager::IsHandlingUserInput() || IsCallerChrome(); } /* static */ bool -nsContentUtils::IsFullscreenApiContentOnly() -{ - return sFullscreenApiIsContentOnly; -} - -/* static */ -bool nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2) { if (!aDoc1 || !aDoc2) { return false; } bool principalsEqual = false; aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual); return principalsEqual; @@ -6747,26 +6733,26 @@ nsContentUtils::FireMutationEventsForDir childNodes.AppendElement(child); } FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes); } } /* static */ nsIDocument* -nsContentUtils::GetFullscreenAncestor(nsIDocument* aDoc) -{ +nsContentUtils::GetRootDocument(nsIDocument* aDoc) +{ + if (!aDoc) { + return nullptr; + } nsIDocument* doc = aDoc; - while (doc) { - if (doc->IsFullScreenDoc()) { - return doc; - } + while (doc->GetParentDocument()) { doc = doc->GetParentDocument(); } - return nullptr; + return doc; } /* static */ bool nsContentUtils::IsInPointerLockContext(nsIDOMWindow* aWin) { if (!aWin) { return false;
--- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1855,42 +1855,16 @@ public: /** * Returns true if requests for full-screen are allowed in the current * context. Requests are only allowed if the user initiated them (like with * a mouse-click or key press), unless this check has been disabled by * setting the pref "full-screen-api.allow-trusted-requests-only" to false. */ static bool IsRequestFullScreenAllowed(); - /** - * Returns true if the DOM fullscreen API is restricted to content only. - * This mirrors the pref "full-screen-api.content-only". If this is true, - * fullscreen requests in chrome are denied, and fullscreen requests in - * content stop percolating upwards before they reach chrome documents. - * That is, when an element in content requests fullscreen, only its - * containing frames that are in content are also made fullscreen, not - * the containing frame in the chrome document. - * - * Note if the fullscreen API is running in content only mode then multiple - * branches of a doctree can be fullscreen at the same time, but no fullscreen - * document will have a common ancestor with another fullscreen document - * that is also fullscreen (since the only common ancestor they can have - * is the chrome document, and that can't be fullscreen). i.e. multiple - * child documents of the chrome document can be fullscreen, but the chrome - * document won't be fullscreen. - * - * Making the fullscreen API content only is useful on platforms where we - * still want chrome to be visible or accessible while content is - * fullscreen. - * - * Note that if the fullscreen API is content only, chrome can still go - * fullscreen by setting the "fullScreen" attribute on its XUL window. - */ - static bool IsFullscreenApiContentOnly(); - /* * Returns true if the performance timing APIs are enabled. */ static bool IsPerformanceTimingEnabled() { return sIsPerformanceTimingEnabled; } @@ -1943,22 +1917,20 @@ public: * Returns true if the content is in a document and contains a plugin * which we don't control event dispatch for, i.e. do any plugins in this * doc tree receive key events outside of our control? This always returns * false on MacOSX. */ static bool HasPluginWithUncontrolledEventDispatch(nsIContent* aContent); /** - * Returns the document that is the closest ancestor to aDoc that is - * fullscreen. If aDoc is fullscreen this returns aDoc. If aDoc is not - * fullscreen and none of aDoc's ancestors are fullscreen this returns - * nullptr. + * Returns the root document in a document hierarchy. Normally this + * will be the chrome document. */ - static nsIDocument* GetFullscreenAncestor(nsIDocument* aDoc); + static nsIDocument* GetRootDocument(nsIDocument* aDoc); /** * Returns true if aWin and the current pointer lock document * have common scriptable top window. */ static bool IsInPointerLockContext(nsIDOMWindow* aWin); /** @@ -2447,17 +2419,16 @@ private: static uint32_t sScriptBlockerCountWhereRunnersPrevented; static nsIInterfaceRequestor* sSameOriginChecker; static bool sIsHandlingKeyBoardEvent; static bool sAllowXULXBL_for_file; static bool sIsFullScreenApiEnabled; static bool sTrustedFullScreenOnly; - static bool sFullscreenApiIsContentOnly; static uint32_t sHandlingInputTimeout; static bool sIsPerformanceTimingEnabled; static bool sIsResourceTimingEnabled; static bool sIsUserTimingLoggingEnabled; static bool sIsExperimentalAutocompleteEnabled; static bool sEncodeDecodeURLHash; static nsHtml5StringParser* sHTMLFragmentParser;
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -11046,45 +11046,27 @@ public: } private: nsCOMPtr<nsIDocument> mDoc; bool mValue; nsRefPtr<gfx::VRHMDInfo> mHMD; }; -static nsIDocument* -GetFullscreenRootDocument(nsIDocument* aDoc) -{ - if (!aDoc) { - return nullptr; - } - nsIDocument* doc = aDoc; - nsIDocument* parent; - while ((parent = doc->GetParentDocument()) && - (!nsContentUtils::IsFullscreenApiContentOnly() || - !nsContentUtils::IsChromeDoc(parent))) { - doc = parent; - } - return doc; -} - static void SetWindowFullScreen(nsIDocument* aDoc, bool aValue, gfx::VRHMDInfo *aVRHMD = nullptr) { // Maintain list of fullscreen root documents. - nsCOMPtr<nsIDocument> root = GetFullscreenRootDocument(aDoc); + nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc); if (aValue) { FullscreenRoots::Add(root); } else { FullscreenRoots::Remove(root); } - if (!nsContentUtils::IsFullscreenApiContentOnly()) { - nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue, aVRHMD)); - } + nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue, aVRHMD)); } class nsCallExitFullscreen : public nsRunnable { public: explicit nsCallExitFullscreen(nsIDocument* aDoc) : mDoc(aDoc) {} NS_IMETHOD Run() { @@ -11279,35 +11261,31 @@ GetFullscreenLeaf(nsIDocument* aDoc) { nsIDocument* leaf = nullptr; GetFullscreenLeaf(aDoc, &leaf); if (leaf) { return leaf; } // Otherwise we could be either in a non-fullscreen doc tree, or we're // below the fullscreen doc. Start the search from the root. - nsIDocument* root = GetFullscreenRootDocument(aDoc); + nsIDocument* root = nsContentUtils::GetRootDocument(aDoc); // Check that the root is actually fullscreen so we don't waste time walking // around its descendants. if (!root->IsFullScreenDoc()) { return nullptr; } GetFullscreenLeaf(root, &leaf); return leaf; } void nsDocument::RestorePreviousFullScreenState() { NS_ASSERTION(!IsFullScreenDoc() || !FullscreenRoots::IsEmpty(), "Should have at least 1 fullscreen root when fullscreen!"); - NS_ASSERTION(!nsContentUtils::IsFullscreenApiContentOnly() || - !nsContentUtils::IsChromeDoc(this), - "Should not run RestorePreviousFullScreenState() on " - "chrome document when fullscreen is content only"); if (!IsFullScreenDoc() || !GetWindow() || FullscreenRoots::IsEmpty()) { return; } // If fullscreen mode is updated the pointer should be unlocked nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); @@ -11372,30 +11350,30 @@ nsDocument::RestorePreviousFullScreenSta } if (!nsContentUtils::HaveEqualPrincipals(doc, fullScreenDoc)) { // The origin which is fullscreen changed. Send a notification to // the root process so that a warning or approval UI can be shown // as necessary. nsAutoString origin; nsContentUtils::GetUTFOrigin(doc->NodePrincipal(), origin); - nsIDocument* root = GetFullscreenRootDocument(doc); + nsIDocument* root = nsContentUtils::GetRootDocument(doc); nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); os->NotifyObservers(root, "fullscreen-origin-change", origin.get()); } break; } } if (doc == nullptr) { // We moved all documents in this doctree out of fullscreen mode, // move the top-level window out of fullscreen mode. - NS_ASSERTION(!GetFullscreenRootDocument(this)->IsFullScreenDoc(), - "Should have cleared all docs' stacks"); + NS_ASSERTION(!nsContentUtils::GetRootDocument(this)->IsFullScreenDoc(), + "Should have cleared all docs' stacks"); nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( this, NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true); asyncDispatcher->PostDOMEvent(); SetWindowFullScreen(this, false); } } bool @@ -11472,18 +11450,17 @@ LogFullScreenDenied(bool aLogFailure, co NS_LITERAL_CSTRING("DOM"), aDoc, nsContentUtils::eDOM_PROPERTIES, aMessage); } nsresult nsDocument::AddFullscreenApprovedObserver() { - if (mHasFullscreenApprovedObserver || - !Preferences::GetBool("full-screen-api.approval-required")) { + if (mHasFullscreenApprovedObserver) { return NS_OK; } nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); NS_ENSURE_TRUE(os, NS_ERROR_FAILURE); nsresult res = os->AddObserver(this, "fullscreen-approved", true); NS_ENSURE_SUCCESS(res, res); @@ -11661,17 +11638,17 @@ nsresult nsDocument::RemoteFrameFullscre // Origin changed in child process, send notifiction, so that chrome can // update the UI to reflect the fullscreen origin change if necessary. // The BrowserElementChild listens on this, and forwards it over its // parent process, where it is redispatched. Chrome (in the root process, // which could be *this* process) listens for this notification so that // it can show a warning or approval UI. if (!aOrigin.IsEmpty()) { nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - os->NotifyObservers(GetFullscreenRootDocument(this), + os->NotifyObservers(nsContentUtils::GetRootDocument(this), "fullscreen-origin-change", PromiseFlatString(aOrigin).get()); } return NS_OK; } nsresult nsDocument::RemoteFrameFullscreenReverted() @@ -11706,23 +11683,16 @@ nsDocument::RequestFullScreen(Element* a if (aElement->OwnerDoc() != this) { LogFullScreenDenied(true, "FullScreenDeniedMovedDocument", this); return; } if (!GetWindow()) { LogFullScreenDenied(true, "FullScreenDeniedLostWindow", this); return; } - if (nsContentUtils::IsFullscreenApiContentOnly() && - nsContentUtils::IsChromeDoc(this)) { - // Block fullscreen requests in the chrome document when the fullscreen API - // is configured for content only. - LogFullScreenDenied(true, "FullScreenDeniedContentOnly", this); - return; - } if (!IsFullScreenEnabled(aWasCallerChrome, true)) { // IsFullScreenEnabled calls LogFullScreenDenied, no need to log. return; } if (GetFullScreenElement() && !nsContentUtils::ContentIsDescendantOf(aElement, GetFullScreenElement())) { // If this document is full-screen, only grant full-screen requests from // a descendant of the current full-screen element. @@ -11759,17 +11729,17 @@ nsDocument::RequestFullScreen(Element* a // too. We're required by the spec to dispatch the events in root-to-leaf // order, but we traverse the doctree in a leaf-to-root order, so we save // references to the documents we must dispatch to so that we get the order // as specified. nsAutoTArray<nsIDocument*, 8> changed; // Remember the root document, so that if a full-screen document is hidden // we can reset full-screen state in the remaining visible full-screen documents. - nsIDocument* fullScreenRootDoc = GetFullscreenRootDocument(this); + nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this); if (fullScreenRootDoc->IsFullScreenDoc()) { // A document is already in fullscreen, unlock the mouse pointer // before setting a new document to fullscreen UnlockPointer(); } // If a document is already in fullscreen, then unlock the mouse pointer // before setting a new document to fullscreen @@ -11873,17 +11843,17 @@ nsDocument::RequestFullScreen(Element* a // root document knows the origin of the document which requested fullscreen. // This is used for the fullscreen approval UI. If we're in a child // process, the root BrowserElementChild listens for this notification, // and forwards it across to its BrowserElementParent, which // re-broadcasts the message for the root document in its process. if (aNotifyOnOriginChange && !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) { nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); - nsIDocument* root = GetFullscreenRootDocument(this); + nsIDocument* root = nsContentUtils::GetRootDocument(this); nsAutoString origin; nsContentUtils::GetUTFOrigin(NodePrincipal(), origin); os->NotifyObservers(root, "fullscreen-origin-change", origin.get()); } // Make the window full-screen. Note we must make the state changes above // before making the window full-screen, as then the document reports as // being in full-screen mode when the chrome "fullscreen" event fires,
--- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -1226,26 +1226,25 @@ nsFocusManager::SetFocusInner(nsIContent } // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX // system. We don't control event dispatch to windowed plugins on non-MacOSX, // so we can't display the "Press ESC to leave fullscreen mode" warning on // key input if a windowed plugin is focused, so just exit fullscreen // to guard against phishing. #ifndef XP_MACOSX - nsIDocument* fullscreenAncestor; if (contentToFocus && - (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) && + nsContentUtils::GetRootDocument(contentToFocus->OwnerDoc())->IsFullScreenDoc() && nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), contentToFocus->OwnerDoc(), nsContentUtils::eDOM_PROPERTIES, "FocusedWindowedPluginWhileFullScreen"); - nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true); + nsIDocument::ExitFullscreen(contentToFocus->OwnerDoc(), /* async */ true); } #endif // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be // shifted away from the current element if the new shell to focus is // the same or an ancestor shell of the currently focused shell. bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) || IsSameOrAncestor(newWindow, mFocusedWindow);
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -1156,20 +1156,17 @@ public: * If aDocument is null, all fullscreen documents in all browser windows * exit fullscreen. * * If aDocument is non null, all documents from aDocument's fullscreen root * to the fullscreen leaf exit fullscreen. * * Note that the fullscreen leaf is the bottom-most document which is * fullscreen, it may have non-fullscreen child documents. The fullscreen - * root is usually the chrome document, but if fullscreen is content-only, - * (see the comment in nsContentUtils.h on IsFullscreenApiContentOnly()) - * the fullscreen root will be a direct child of the chrome document, and - * there may be other branches of the same doctree that are fullscreen. + * root is normally the chrome document. * * If aRunAsync is true, fullscreen is executed asynchronously. * * Note if aDocument is not fullscreen this function has no effect, even if * aDocument has fullscreen ancestors. */ static void ExitFullscreen(nsIDocument* aDocument, bool aRunAsync);
--- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -1048,16 +1048,17 @@ ContentEventHandler::OnQueryCaretRect(Wi if (offset == aEvent->mInput.mOffset) { if (!caretFrame) { return NS_ERROR_FAILURE; } rv = ConvertToRootViewRelativeOffset(caretFrame, caretRect); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped( caretRect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel())); + aEvent->mReply.mWritingMode = caretFrame->GetWritingMode(); aEvent->mReply.mOffset = aEvent->mInput.mOffset; aEvent->mSucceeded = true; return NS_OK; } } // Otherwise, we should set the guessed caret rect. nsRefPtr<nsRange> range = new nsRange(mRootContent); @@ -1081,16 +1082,17 @@ ContentEventHandler::OnQueryCaretRect(Wi rect.width = caretRect.width; rect.height = frame->GetSize().height; rv = ConvertToRootViewRelativeOffset(frame, rect); NS_ENSURE_SUCCESS(rv, rv); aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped( rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel())); + aEvent->mReply.mWritingMode = frame->GetWritingMode(); aEvent->mSucceeded = true; return NS_OK; } nsresult ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent) { nsresult rv = Init(aEvent);
--- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -27,16 +27,17 @@ #include "nsIPresShell.h" #include "nsISelectionController.h" #include "nsISelectionPrivate.h" #include "nsISupports.h" #include "nsIWidget.h" #include "nsPresContext.h" #include "nsThreadUtils.h" #include "nsWeakReference.h" +#include "WritingModes.h" namespace mozilla { using namespace widget; NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver) @@ -242,16 +243,22 @@ IMEContentObserver::UnregisterObservers( } if (mUpdatePreference.WantPositionChanged() && mDocShell) { mDocShell->RemoveWeakScrollObserver(this); mDocShell->RemoveWeakReflowObserver(this); } } +nsPresContext* +IMEContentObserver::GetPresContext() const +{ + return mESM ? mESM->GetPresContext() : nullptr; +} + void IMEContentObserver::Destroy() { // WARNING: When you change this method, you have to check Unlink() too. UnregisterObservers(false); mEditor = nullptr; @@ -326,22 +333,49 @@ public: : mDispatcher(aDispatcher) , mCausedByComposition(aCausedByComposition) { MOZ_ASSERT(mDispatcher); } NS_IMETHOD Run() { - if (mDispatcher->GetWidget()) { - IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); - notification.mSelectionChangeData.mCausedByComposition = - mCausedByComposition; - mDispatcher->GetWidget()->NotifyIME(notification); + nsCOMPtr<nsIWidget> widget = mDispatcher->GetWidget(); + nsPresContext* presContext = mDispatcher->GetPresContext(); + if (!widget || !presContext) { + return NS_OK; + } + + // XXX Cannot we cache some information for reducing the cost to compute + // selection offset and writing mode? + WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, widget); + ContentEventHandler handler(presContext); + handler.OnQuerySelectedText(&selection); + if (NS_WARN_IF(!selection.mSucceeded)) { + return NS_OK; } + + // The widget might be destroyed during querying the content since it + // causes flushing layout. + widget = mDispatcher->GetWidget(); + if (!widget || NS_WARN_IF(widget->Destroyed())) { + return NS_OK; + } + + IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); + notification.mSelectionChangeData.mOffset = + selection.mReply.mOffset; + notification.mSelectionChangeData.mLength = + selection.mReply.mString.Length(); + notification.mSelectionChangeData.SetWritingMode( + selection.GetWritingMode()); + notification.mSelectionChangeData.mReversed = selection.mReply.mReversed; + notification.mSelectionChangeData.mCausedByComposition = + mCausedByComposition; + widget->NotifyIME(notification); return NS_OK; } private: nsRefPtr<IMEContentObserver> mDispatcher; bool mCausedByComposition; };
--- a/dom/events/IMEContentObserver.h +++ b/dom/events/IMEContentObserver.h @@ -74,16 +74,17 @@ public: void DisconnectFromEventStateManager(); bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent); bool IsEditorHandlingEventForComposition() const; bool KeepAliveDuringDeactive() const { return mUpdatePreference.WantDuringDeactive(); } nsIWidget* GetWidget() const { return mWidget; } + nsPresContext* GetPresContext() const; nsresult GetSelectionAndRoot(nsISelection** aSelection, nsIContent** aRoot) const; struct TextChangeData { // mStartOffset is the start offset of modified or removed text in // original content and inserted text in new content. uint32_t mStartOffset;
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -1889,16 +1889,25 @@ TabParent::RecvNotifyIMESelection(const mIMESelectionFocus = aFocus; mWritingMode = aWritingMode; const nsIMEUpdatePreference updatePreference = widget->GetIMEUpdatePreference(); if (updatePreference.WantSelectionChange() && (updatePreference.WantChangesCausedByComposition() || !aCausedByComposition)) { IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); + notification.mSelectionChangeData.mOffset = + std::min(mIMESelectionAnchor, mIMESelectionFocus); + notification.mSelectionChangeData.mLength = + mIMESelectionAnchor > mIMESelectionFocus ? + mIMESelectionAnchor - mIMESelectionFocus : + mIMESelectionFocus - mIMESelectionAnchor; + notification.mSelectionChangeData.mReversed = + mIMESelectionFocus < mIMESelectionAnchor; + notification.mSelectionChangeData.SetWritingMode(mWritingMode); notification.mSelectionChangeData.mCausedByComposition = aCausedByComposition; widget->NotifyIME(notification); } } return true; }
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -74,17 +74,16 @@ FullScreenDeniedHidden=Request for full- FullScreenDeniedIframeNotAllowed=Request for full-screen was denied because at least one of the document's containing iframes does not have an "allowfullscreen" attribute. FullScreenDeniedNotInputDriven=Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler. FullScreenDeniedNotInDocument=Request for full-screen was denied because requesting element is no longer in its document. FullScreenDeniedMovedDocument=Request for full-screen was denied because requesting element has moved document. FullScreenDeniedLostWindow=Request for full-screen was denied because we no longer have a window. FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen. FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element. FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab. -FullScreenDeniedContentOnly=Request for full-screen was denied because requesting element is in the chrome document and the fullscreen API is configured for content only. RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document. FocusedWindowedPluginWhileFullScreen=Exited full-screen because windowed plugin was focused. HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode. InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn't implement nsIWritablePropertyBag2. ResponseTypeSyncXHRWarning=Use of XMLHttpRequest's responseType attribute is no longer supported in the synchronous mode in window context. WithCredentialsSyncXHRWarning=Use of XMLHttpRequest's withCredentials attribute is no longer supported in the synchronous mode in window context. TimeoutSyncXHRWarning=Use of XMLHttpRequest's timeout attribute is not supported in the synchronous mode in window context. JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
--- a/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp @@ -200,24 +200,27 @@ WMFAudioMFTManager::UpdateOutputType() HRESULT WMFAudioMFTManager::Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutData) { aOutData = nullptr; RefPtr<IMFSample> sample; HRESULT hr; + bool alreadyDidTypeChange = false; while (true) { hr = mDecoder->Output(&sample); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { return hr; } if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { hr = UpdateOutputType(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + NS_ENSURE_FALSE(alreadyDidTypeChange, MF_E_TRANSFORM_STREAM_CHANGE); + alreadyDidTypeChange = true; continue; } break; } NS_ENSURE_TRUE(SUCCEEDED(hr), hr); RefPtr<IMFMediaBuffer> buffer;
--- a/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp +++ b/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WMFMediaDataDecoder.h" #include "VideoUtils.h" #include "WMFUtils.h" #include "nsTArray.h" +#include "mozilla/Telemetry.h" #include "prlog.h" PRLogModuleInfo* GetDemuxerLog(); #define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) namespace mozilla { @@ -39,16 +40,49 @@ WMFMediaDataDecoder::Init() MOZ_ASSERT(!mIsShutDown); mDecoder = mMFTManager->Init(); NS_ENSURE_TRUE(mDecoder, NS_ERROR_FAILURE); return NS_OK; } +// A single telemetry sample is reported for each MediaDataDecoder object +// that has detected error or produced output successfully. +static void +SendTelemetry(HRESULT hr) +{ + // Collapse the error codes into a range of 0-0xff that can be viewed in + // telemetry histograms. For most MF_E_* errors, unique samples are used, + // retaining the least significant 7 or 8 bits. Other error codes are + // bucketed. + uint32_t sample; + if (SUCCEEDED(hr)) { + sample = 0; + } else if (hr < 0xc00d36b0) { + sample = 1; // low bucket + } else if (hr < 0xc00d3700) { + sample = hr & 0xffU; // MF_E_* + } else if (hr <= 0xc00d3705) { + sample = 0x80 + (hr & 0xfU); // more MF_E_* + } else if (hr < 0xc00d6d60) { + sample = 2; // mid bucket + } else if (hr <= 0xc00d6d78) { + sample = hr & 0xffU; // MF_E_TRANSFORM_* + } else { + sample = 3; // high bucket + } + + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + [sample] { + Telemetry::Accumulate(Telemetry::MEDIA_WMF_DECODE_ERROR, sample); + }); + NS_DispatchToMainThread(runnable); +} + nsresult WMFMediaDataDecoder::Shutdown() { MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); if (mTaskQueue) { mTaskQueue->Dispatch( NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessShutdown)); @@ -60,16 +94,19 @@ WMFMediaDataDecoder::Shutdown() } void WMFMediaDataDecoder::ProcessShutdown() { if (mMFTManager) { mMFTManager->Shutdown(); mMFTManager = nullptr; + if (!mRecordedError && mHasSuccessfulOutput) { + SendTelemetry(S_OK); + } } mDecoder = nullptr; } // Inserts data into the decoder's pipeline. nsresult WMFMediaDataDecoder::Input(MediaRawData* aSample) { @@ -95,40 +132,49 @@ WMFMediaDataDecoder::ProcessDecode(Media return; } } HRESULT hr = mMFTManager->Input(aSample); if (FAILED(hr)) { NS_WARNING("MFTManager rejected sample"); mCallback->Error(); + if (!mRecordedError) { + SendTelemetry(hr); + mRecordedError = true; + } return; } mLastStreamOffset = aSample->mOffset; ProcessOutput(); } void WMFMediaDataDecoder::ProcessOutput() { nsRefPtr<MediaData> output; HRESULT hr = S_OK; while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output)) && output) { + mHasSuccessfulOutput = true; mCallback->Output(output); } if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { if (mTaskQueue->IsEmpty()) { mCallback->InputExhausted(); } } else if (FAILED(hr)) { NS_WARNING("WMFMediaDataDecoder failed to output data"); mCallback->Error(); + if (!mRecordedError) { + SendTelemetry(hr); + mRecordedError = true; + } } } void WMFMediaDataDecoder::ProcessFlush() { if (mDecoder) { mDecoder->Flush();
--- a/dom/media/fmp4/wmf/WMFMediaDataDecoder.h +++ b/dom/media/fmp4/wmf/WMFMediaDataDecoder.h @@ -104,13 +104,17 @@ private: // For access to and waiting on mIsFlushing Monitor mMonitor; // Set on reader/decode thread calling Flush() to indicate that output is // not required and so input samples on mTaskQueue need not be processed. // Cleared on mTaskQueue. bool mIsFlushing; bool mIsShutDown; + + // For telemetry + bool mHasSuccessfulOutput = false; + bool mRecordedError = false; }; } // namespace mozilla #endif // WMFMediaDataDecoder_h_
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp @@ -477,40 +477,43 @@ WMFVideoMFTManager::CreateD3DVideoFrame( // Blocks until decoded sample is produced by the deoder. HRESULT WMFVideoMFTManager::Output(int64_t aStreamOffset, nsRefPtr<MediaData>& aOutData) { RefPtr<IMFSample> sample; HRESULT hr; aOutData = nullptr; + bool alreadyDidTypeChange = false; // Loop until we decode a sample, or an unexpected error that we can't // handle occurs. while (true) { hr = mDecoder->Output(&sample); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { return MF_E_TRANSFORM_NEED_MORE_INPUT; } if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { // Video stream output type change. Probably a geometric apperature // change. Reconfigure the video geometry, so that we output the // correct size frames. MOZ_ASSERT(!sample); hr = ConfigureVideoFrameGeometry(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + NS_ENSURE_FALSE(alreadyDidTypeChange, MF_E_TRANSFORM_STREAM_CHANGE); // Loop back and try decoding again... + alreadyDidTypeChange = true; continue; } if (SUCCEEDED(hr)) { break; } // Else unexpected error, assert, and bail. NS_WARNING("WMFVideoMFTManager::Output() unexpected error"); - return E_FAIL; + return hr; } nsRefPtr<VideoData> frame; if (mUseHwAccel) { hr = CreateD3DVideoFrame(sample, aStreamOffset, getter_AddRefs(frame)); } else { hr = CreateBasicVideoFrame(sample, aStreamOffset, getter_AddRefs(frame)); }
--- a/dom/media/omx/AudioOffloadPlayer.cpp +++ b/dom/media/omx/AudioOffloadPlayer.cpp @@ -380,23 +380,16 @@ status_t AudioOffloadPlayer::DoSeek() MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility); mSeekPromise.Resolve(val, __func__); } } return OK; } -double AudioOffloadPlayer::GetMediaTimeSecs() -{ - MOZ_ASSERT(NS_IsMainThread()); - return (static_cast<double>(GetMediaTimeUs()) / - static_cast<double>(USECS_PER_S)); -} - int64_t AudioOffloadPlayer::GetMediaTimeUs() { android::Mutex::Autolock autoLock(mLock); int64_t playPosition = 0; if (mSeekTarget.IsValid()) { return mSeekTarget.mTime; }
--- a/dom/media/omx/AudioOffloadPlayer.h +++ b/dom/media/omx/AudioOffloadPlayer.h @@ -83,17 +83,17 @@ public: // Start the source if it's not already started and open the GonkAudioSink to // create an offloaded audio track virtual status_t Start(bool aSourceAlreadyStarted = false) override; virtual status_t ChangeState(MediaDecoder::PlayState aState) override; virtual void SetVolume(double aVolume) override; - virtual double GetMediaTimeSecs() override; + virtual int64_t GetMediaTimeUs() override; // To update progress bar when the element is visible virtual void SetElementVisibility(bool aIsVisible) override;; // Update ready state based on current play state. Not checking data // availability since offloading is currently done only when whole compressed // data is available virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override; @@ -187,18 +187,16 @@ private: // It is triggered in Pause() and canceled when there is a Play() within // OFFLOAD_PAUSE_MAX_USECS. Used only from main thread so no lock is needed. nsCOMPtr<nsITimer> mResetTimer; // To avoid device suspend when mResetTimer is going to be triggered. // Used only from main thread so no lock is needed. nsRefPtr<mozilla::dom::WakeLock> mWakeLock; - int64_t GetMediaTimeUs(); - // Provide the playback position in microseconds from total number of // frames played by audio track int64_t GetOutputPlayPositionUs_l() const; // Fill the buffer given by audio sink with data from compressed audio // source. Also handles the seek by seeking audio source and stop the sink in // case of error size_t FillBuffer(void *aData, size_t aSize);
--- a/dom/media/omx/AudioOffloadPlayerBase.h +++ b/dom/media/omx/AudioOffloadPlayerBase.h @@ -49,17 +49,17 @@ public: virtual status_t ChangeState(MediaDecoder::PlayState aState) { return android::NO_INIT; } virtual void SetVolume(double aVolume) {} - virtual double GetMediaTimeSecs() { return 0; } + virtual int64_t GetMediaTimeUs() { return 0; } // To update progress bar when the element is visible virtual void SetElementVisibility(bool aIsVisible) {} // Update ready state based on current play state. Not checking data // availability since offloading is currently done only when whole compressed // data is available virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus()
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp +++ b/dom/media/omx/MediaOmxCommonDecoder.cpp @@ -239,17 +239,17 @@ int64_t MediaOmxCommonDecoder::CurrentPosition() { MOZ_ASSERT(NS_IsMainThread()); if (!mAudioOffloadPlayer) { return MediaDecoder::CurrentPosition(); } ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - return mAudioOffloadPlayer->GetMediaTimeSecs(); + return mAudioOffloadPlayer->GetMediaTimeUs(); } void MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible) { MOZ_ASSERT(NS_IsMainThread()); if (mAudioOffloadPlayer) { mAudioOffloadPlayer->SetElementVisibility(aIsVisible);
deleted file mode 100644 --- a/dom/media/webspeech/synth/ipc/test/file_ipc.html +++ /dev/null @@ -1,196 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <title>Test for OOP TTS</title> - <script type="application/javascript" src="../../test/common.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> - <body> - - <script type="application/javascript;version=1.7"> - "use strict"; - - window.SimpleTest = parent.SimpleTest; - window.ok = parent.ok; - window.is = parent.is; - window.info = parent.info; - - // The crash observer registration functions are stubbed out here to - // prevent the iframe test runner from breaking later crash-related tests. - function iframeScriptFirst() { - SpecialPowers.prototype.registerProcessCrashObservers = () => {}; - SpecialPowers.prototype.unregisterProcessCrashObservers = () => {}; - - content.wrappedJSObject.RunSet.reloadAndRunAll({ - preventDefault: function() { }, - __exposedProps__: { preventDefault: 'r' } - }); - } - - function iframeScriptSecond() { - let TestRunner = content.wrappedJSObject.TestRunner; - - let oldComplete = TestRunner.onComplete; - - TestRunner.onComplete = function() { - TestRunner.onComplete = oldComplete; - - sendAsyncMessage("test:SpeechSynthesis:ipcTestComplete", { - result: JSON.stringify(TestRunner._failedTests) - }); - - if (oldComplete) { - oldComplete(); - } - }; - TestRunner.structuredLogger._dumpMessage = function(msg) { - sendAsyncMessage("test:SpeechSynthesis:ipcTestMessage", { msg: msg }); - } - } - - let VALID_ACTIONS = ['suite_start', 'suite_end', 'test_start', 'test_end', 'test_status', 'process_output', 'log']; - function validStructuredMessage(message) { - return message.action !== undefined && VALID_ACTIONS.indexOf(message.action) >= 0; - } - function onTestMessage(data) { - let message = SpecialPowers.wrap(data).data.msg; - - if (validStructuredMessage(message)) { - switch (message.action) { - case "test_status": - case "test_end": - let test_tokens = message.test.split("/"); - let test_name = test_tokens[test_tokens.length - 1]; - if (message.subtest) { - test_name += " | " + message.subtest; - } - ok(message.expected === undefined, test_name, message.message); - break; - case "log": - info(message.message); - break; - default: - // nothing - } - } - } - - function onTestComplete() { - let comp = SpecialPowers.wrap(SpecialPowers.Components); - let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); - let spObserver = comp.classes["@mozilla.org/special-powers-observer;1"] - .getService(comp.interfaces.nsIMessageListener); - - mm.removeMessageListener("SPPrefService", spObserver); - mm.removeMessageListener("SPProcessCrashService", spObserver); - mm.removeMessageListener("SPPingService", spObserver); - mm.removeMessageListener("SpecialPowers.Quit", spObserver); - mm.removeMessageListener("SPPermissionManager", spObserver); - - mm.removeMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage); - mm.removeMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete); - - let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"] - .getService(SpecialPowers.Ci.nsIMessageBroadcaster); - ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice); - ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault); - ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup); - - SimpleTest.executeSoon(function () { SimpleTest.finish(); }); - } - - function onSynthAddVoice(data) { - let message = SpecialPowers.wrap(data).json; - return synthAddVoice.apply(synthAddVoice, message); - } - - function onSynthSetDefault(data) { - let message = SpecialPowers.wrap(data).json; - synthSetDefault.apply(synthSetDefault, message); - } - - function onSynthCleanup(data) { - synthCleanup(); - } - - function runTests() { - let iframe = document.createElement("iframe"); - SpecialPowers.wrap(iframe).mozbrowser = true; - iframe.id = "iframe"; - iframe.style.width = "100%"; - iframe.style.height = "1000px"; - - function iframeLoadSecond() { - ok(true, "Got second iframe load event."); - iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond); - let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); - mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();", - false); - } - - function iframeLoadFirst() { - ok(true, "Got first iframe load event."); - iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst); - iframe.addEventListener("mozbrowserloadend", iframeLoadSecond); - - let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); - - let comp = SpecialPowers.wrap(SpecialPowers.Components); - - let spObserver = - comp.classes["@mozilla.org/special-powers-observer;1"] - .getService(comp.interfaces.nsIMessageListener); - - mm.addMessageListener("SPPrefService", spObserver); - mm.addMessageListener("SPProcessCrashService", spObserver); - mm.addMessageListener("SPPingService", spObserver); - mm.addMessageListener("SpecialPowers.Quit", spObserver); - mm.addMessageListener("SPPermissionManager", spObserver); - - mm.addMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage); - mm.addMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete); - - let specialPowersBase = "chrome://specialpowers/content/"; - mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false); - mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false); - mm.loadFrameScript(specialPowersBase + "specialpowers.js", false); - - mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false); - - let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"] - .getService(SpecialPowers.Ci.nsIMessageBroadcaster); - ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice); - ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault); - ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup); - } - - iframe.addEventListener("mozbrowserloadend", iframeLoadFirst); - - // Strip this filename and one directory level and then add "/test". - let href = window.location.href; - href = href.substring(0, href.lastIndexOf('/')); - href = href.substring(0, href.lastIndexOf('/')); - href = href.substring(0, href.lastIndexOf('/')); - iframe.src = href + "/test?consoleLevel=INFO"; - - document.body.appendChild(iframe); - } - - addEventListener("load", function() { - - SpecialPowers.addPermission("browser", true, document); - SpecialPowers.pushPrefEnv({ - "set": [ - // TODO: remove this as part of bug 820712 - ["network.disable.ipc.security", true], - - ["dom.ipc.browser_frames.oop_by_default", true], - ["dom.mozBrowserFramesEnabled", true], - ["browser.pagethumbnails.capturing_disabled", true] - ] - }, runTests); - }); - - </script> -</body> -</html>
deleted file mode 100644 --- a/dom/media/webspeech/synth/ipc/test/mochitest.ini +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] -skip-if = e10s -support-files = - file_ipc.html - -[test_ipc.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 857673 # b2g(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-debug(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-desktop(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined)
deleted file mode 100644 --- a/dom/media/webspeech/synth/ipc/test/test_ipc.html +++ /dev/null @@ -1,17 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <title>Test for OOP TTS</title> - <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> - <body> - <iframe id="testFrame"></iframe> - <script type="application/javascript;version=1.7"> - SimpleTest.waitForExplicitFinish(); - - SpecialPowers.pushPrefEnv({ set: [['media.webspeech.synth.enabled', true]] }, - function() { document.getElementById("testFrame").src = "file_ipc.html"; }); - </script> -</body> -</html>
--- a/dom/media/webspeech/synth/moz.build +++ b/dom/media/webspeech/synth/moz.build @@ -1,16 +1,15 @@ # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. if CONFIG['MOZ_WEBSPEECH']: MOCHITEST_MANIFESTS += [ - 'ipc/test/mochitest.ini', 'test/mochitest.ini', ] XPIDL_MODULE = 'dom_webspeechsynth' XPIDL_SOURCES += [ 'nsISpeechService.idl', 'nsISynthVoiceRegistry.idl' @@ -29,16 +28,18 @@ if CONFIG['MOZ_WEBSPEECH']: UNIFIED_SOURCES += [ 'ipc/SpeechSynthesisChild.cpp', 'ipc/SpeechSynthesisParent.cpp', 'nsSpeechTask.cpp', 'nsSynthVoiceRegistry.cpp', 'SpeechSynthesis.cpp', 'SpeechSynthesisUtterance.cpp', 'SpeechSynthesisVoice.cpp', + 'test/FakeSynthModule.cpp', + 'test/nsFakeSynthServices.cpp' ] if CONFIG['MOZ_SYNTH_PICO']: DIRS = ['pico'] IPDL_SOURCES += [ 'ipc/PSpeechSynthesis.ipdl', 'ipc/PSpeechSynthesisRequest.ipdl',
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp +++ b/dom/media/webspeech/synth/nsSpeechTask.cpp @@ -79,17 +79,17 @@ private: // and 'mSpeechTask' exclusively owns it and therefor exists as well. nsSpeechTask* mSpeechTask; bool mStarted; }; // nsSpeechTask -NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance); +NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback); NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask) NS_INTERFACE_MAP_ENTRY(nsISpeechTask) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeechTask) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
new file mode 100644 --- /dev/null +++ b/dom/media/webspeech/synth/test/FakeSynthModule.cpp @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsIClassInfoImpl.h" + +#include "nsFakeSynthServices.h" + +using namespace mozilla::dom; + +#define FAKESYNTHSERVICE_CID \ + {0xe7d52d9e, 0xc148, 0x47d8, {0xab, 0x2a, 0x95, 0xd7, 0xf4, 0x0e, 0xa5, 0x3d}} + +#define FAKESYNTHSERVICE_CONTRACTID "@mozilla.org/fakesynth;1" + +// Defines nsFakeSynthServicesConstructor +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFakeSynthServices, + nsFakeSynthServices::GetInstanceForService) + +// Defines kFAKESYNTHSERVICE_CID +NS_DEFINE_NAMED_CID(FAKESYNTHSERVICE_CID); + +static const mozilla::Module::CIDEntry kCIDs[] = { + { &kFAKESYNTHSERVICE_CID, true, nullptr, nsFakeSynthServicesConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kContracts[] = { + { FAKESYNTHSERVICE_CONTRACTID, &kFAKESYNTHSERVICE_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kCategories[] = { + { "profile-after-change", "Fake Speech Synth", FAKESYNTHSERVICE_CONTRACTID }, + { nullptr } +}; + +static void +UnloadFakeSynthmodule() +{ + nsFakeSynthServices::Shutdown(); +} + +static const mozilla::Module kModule = { + mozilla::Module::kVersion, + kCIDs, + kContracts, + kCategories, + nullptr, + nullptr, + UnloadFakeSynthmodule +}; + +NSMODULE_DEFN(fakesynth) = &kModule;
--- a/dom/media/webspeech/synth/test/common.js +++ b/dom/media/webspeech/synth/test/common.js @@ -1,204 +1,8 @@ -var gSpeechRegistry = SpecialPowers.Cc["@mozilla.org/synth-voice-registry;1"] - .getService(SpecialPowers.Ci.nsISynthVoiceRegistry); - -var gAddedVoices = []; - -function SpeechTaskCallback(onpause, onresume, oncancel) { - this.onpause = onpause; - this.onresume = onresume; - this.oncancel = oncancel; -} - -SpeechTaskCallback.prototype = { - QueryInterface: function(iid) { - return this; - }, - - getInterfaces: function(c) {}, - - getScriptableHelper: function() {}, - - onPause: function onPause() { - if (this.onpause) - this.onpause(); - }, - - onResume: function onResume() { - if (this.onresume) - this.onresume(); - }, - - onCancel: function onCancel() { - if (this.oncancel) - this.oncancel(); - } -}; - -var TestSpeechServiceWithAudio = SpecialPowers.wrapCallbackObject({ - CHANNELS: 1, - SAMPLE_RATE: 16000, - - serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_DIRECT_AUDIO, - - speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) { - var task = SpecialPowers.wrap(aTask); - - window.setTimeout( - function () { - task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback()), this.CHANNELS, this.SAMPLE_RATE); - // 0.025 seconds per character. - task.sendAudio(new Int16Array((this.SAMPLE_RATE/40)*aText.length), []); - task.sendAudio(new Int16Array(0), []); - }.bind(this), 0); - }, - - QueryInterface: function(iid) { - return this; - }, - - getInterfaces: function(c) {}, - - getScriptableHelper: function() {} -}); - -var TestSpeechServiceNoAudio = SpecialPowers.wrapCallbackObject({ - serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_INDIRECT_AUDIO, - - speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) { - var pair = this.expectedSpeaks.shift(); - if (pair) { - // XXX: These tests do not happen in OOP - var utterance = pair[0]; - var expected = pair[1]; - - is(aText, utterance.text, "Speak text matches utterance text"); - - var args = {uri: aUri, rate: aRate, pitch: aPitch}; - - for (var attr in args) { - if (expected[attr] != undefined) - is(args[attr], expected[attr], "expected service arg " + attr); - } - } - - // If the utterance contains the phrase 'callback events', we will dispatch - // an appropriate event for each callback method. - var no_events = (aText.indexOf('callback events') < 0); - // If the utterance contains the phrase 'never end', we don't immediately - // end the 'synthesis' of the utterance. - var end_utterance = (aText.indexOf('never end') < 0); - - var task = SpecialPowers.wrap(aTask); - task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback( - function() { - if (!no_events) { - task.dispatchPause(1, 1.23); - } - }, - function() { - if (!no_events) { - task.dispatchResume(1, 1.23); - } - }, - function() { - if (!no_events) { - task.dispatchEnd(1, 1.23); - } - }))); - setTimeout(function () { - task.dispatchStart(); - if (end_utterance) { - setTimeout(function () { - task.dispatchEnd( - aText.length / 2.0, aText.length); - }, 0); - } - }, 0); - }, - - QueryInterface: function(iid) { - return this; - }, - - getInterfaces: function(c) {}, - - getScriptableHelper: function() {}, - - expectedSpeaks: [] -}); - -function synthAddVoice(aServiceName, aName, aLang, aIsLocal) { - if (SpecialPowers.isMainProcess()) { - var voicesBefore = speechSynthesis.getVoices().length; - var uri = "urn:moz-tts:mylittleservice:" + encodeURI(aName + '?' + aLang); - gSpeechRegistry.addVoice(window[aServiceName], uri, aName, aLang, aIsLocal); - - gAddedVoices.push([window[aServiceName], uri]); - var voicesAfter = speechSynthesis.getVoices().length; - - is(voicesBefore + 1, voicesAfter, "Voice added"); - var voice = speechSynthesis.getVoices()[voicesAfter - 1]; - is(voice.voiceURI, uri, "voice URI matches"); - is(voice.name, aName, "voice name matches"); - is(voice.lang, aLang, "voice lang matches"); - is(voice.localService, aIsLocal, "voice localService matches"); - - return uri; - } else { - // XXX: It would be nice to check here that the child gets the voice - // added update, but alas, it is aynchronous. - var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"] - .getService(SpecialPowers.Ci.nsISyncMessageSender); - - return mm.sendSyncMessage( - 'test:SpeechSynthesis:ipcSynthAddVoice', - [aServiceName, aName, aLang, aIsLocal])[0]; - } -} - -function synthSetDefault(aUri, aIsDefault) { - if (SpecialPowers.isMainProcess()) { - gSpeechRegistry.setDefaultVoice(aUri, aIsDefault); - var voices = speechSynthesis.getVoices(); - for (var i in voices) { - if (voices[i].voiceURI == aUri) - ok(voices[i]['default'], "Voice set to default"); - } - } else { - // XXX: It would be nice to check here that the child gets the voice - // added update, but alas, it is aynchronous. - var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"] - .getService(SpecialPowers.Ci.nsISyncMessageSender); - - return mm.sendSyncMessage( - 'test:SpeechSynthesis:ipcSynthSetDefault', [aUri, aIsDefault])[0]; - } -} - -function synthCleanup() { - if (SpecialPowers.isMainProcess()) { - var voicesBefore = speechSynthesis.getVoices().length; - var toRemove = gAddedVoices.length; - var removeArgs; - while ((removeArgs = gAddedVoices.shift())) - gSpeechRegistry.removeVoice.apply(gSpeechRegistry.removeVoice, removeArgs); - - var voicesAfter = speechSynthesis.getVoices().length; - is(voicesAfter, voicesBefore - toRemove, "Successfully removed test voices"); - } else { - // XXX: It would be nice to check here that the child gets the voice - // removed update, but alas, it is aynchronous. - var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"] - .getService(SpecialPowers.Ci.nsISyncMessageSender); - mm.sendSyncMessage('test:SpeechSynthesis:ipcSynthCleanup'); - } -} - function synthTestQueue(aTestArgs, aEndFunc) { var utterances = []; for (var i in aTestArgs) { var uargs = aTestArgs[i][0]; var u = new SpeechSynthesisUtterance(uargs.text); delete uargs.text; @@ -215,24 +19,33 @@ function synthTestQueue(aTestArgs, aEndF ok(speechSynthesis.pending, "other utterances queued"); } else { ok(!speechSynthesis.pending, "queue is empty, nothing pending."); if (aEndFunc) aEndFunc(); } } + u.addEventListener('start', + (function (expectedUri) { + return function (e) { + if (expectedUri) { + var chosenVoice = SpecialPowers.wrap(e).target.chosenVoiceURI; + is(chosenVoice, expectedUri, "Incorrect URI is used"); + } + }; + })(aTestArgs[i][1] ? aTestArgs[i][1].uri : null)); + u.addEventListener('end', onend_handler); u.addEventListener('error', onend_handler); u.addEventListener( 'error', function onerror_handler(e) { ok(false, "Error in speech utterance '" + e.target.text + "'"); }); utterances.push(u); - TestSpeechServiceNoAudio.expectedSpeaks.push([u, aTestArgs[i][1]]); speechSynthesis.speak(u); } ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet."); ok(speechSynthesis.pending, "speechSynthesis has an utterance queued."); }
--- a/dom/media/webspeech/synth/test/file_indirect_service_events.html +++ b/dom/media/webspeech/synth/test/file_indirect_service_events.html @@ -21,56 +21,54 @@ https://bugzilla.mozilla.org/show_bug.cg <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 1155034 **/ -synthAddVoice('TestSpeechServiceNoAudio', 'Female 1', 'en-GB', true); - function test_with_events() { info('test_with_events'); var utterance = new SpeechSynthesisUtterance("never end, callback events"); + utterance.lang = 'it-IT-noend'; utterance.addEventListener('start', function(e) { speechSynthesis.pause(); // Wait to see if we get some bad events we didn't expect. }); utterance.addEventListener('pause', function(e) { - ok(e.charIndex, 1, 'pause event charIndex matches service arguments'); - ok(e.elapsedTime, 1.23, 'pause event elapsedTime matches service arguments'); + is(e.charIndex, 1, 'pause event charIndex matches service arguments'); + is(e.elapsedTime, 1.5, 'pause event elapsedTime matches service arguments'); speechSynthesis.resume(); }); utterance.addEventListener('resume', function(e) { - ok(e.charIndex, 1, 'resume event charIndex matches service arguments'); - ok(e.elapsedTime, 1.23, 'resume event elapsedTime matches service arguments'); + is(e.charIndex, 1, 'resume event charIndex matches service arguments'); + is(e.elapsedTime, 1.5, 'resume event elapsedTime matches service arguments'); speechSynthesis.cancel(); }); utterance.addEventListener('end', function(e) { ok(e.charIndex, 1, 'resume event charIndex matches service arguments'); - ok(e.elapsedTime, 1.23, 'end event elapsedTime matches service arguments'); + ok(e.elapsedTime, 1.5, 'end event elapsedTime matches service arguments'); test_no_events(); }); speechSynthesis.speak(utterance); } function test_no_events() { var utterance = new SpeechSynthesisUtterance("never end"); - + utterance.lang = "it-IT-noevents-noend"; utterance.addEventListener('start', function(e) { speechSynthesis.pause(); // Wait to see if we get some bad events we didn't expect. setTimeout(function() { - synthCleanup(); SimpleTest.finish(); }, 1000); }); utterance.addEventListener('pause', function(e) { ok(false, 'no pause event was explicitly dispatched from the service') speechSynthesis.resume(); });
--- a/dom/media/webspeech/synth/test/file_setup.html +++ b/dom/media/webspeech/synth/test/file_setup.html @@ -20,22 +20,16 @@ https://bugzilla.mozilla.org/show_bug.cg <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 525444 **/ -synthAddVoice('TestSpeechServiceNoAudio', 'Bob Marley', 'en-JM', true); -synthAddVoice('TestSpeechServiceNoAudio', 'Amy Winehouse', 'en-GB', true); -synthAddVoice('TestSpeechServiceNoAudio', 'Leonard Cohen', 'en-CA', true); -synthAddVoice('TestSpeechServiceNoAudio', 'Celine Dion', 'fr-CA', true); -synthAddVoice('TestSpeechServiceNoAudio', 'Julieta Venegas', 'es-MX', true); - ok(SpeechSynthesis, "SpeechSynthesis exists in global scope"); ok(SpeechSynthesisVoice, "SpeechSynthesisVoice exists in global scope"); ok(SpeechSynthesisEvent, "SpeechSynthesisEvent exists in global scope"); // SpeechSynthesisUtterance is the only type that has a constructor // and writable properties ok(SpeechSynthesisUtterance, "SpeechSynthesisUtterance exists in global scope"); var ssu = new SpeechSynthesisUtterance("hello world"); @@ -70,15 +64,13 @@ var voices1 = speechSynthesis.getVoices( var voices2 = speechSynthesis.getVoices(); ok(voices1.length == voices2.length, "Voice count matches"); for (var i in voices1) { ok(voices1[i] == voices2[i], "Voice instance matches"); } -synthCleanup(); - SimpleTest.finish(); </script> </pre> </body> </html>
--- a/dom/media/webspeech/synth/test/file_speech_cancel.html +++ b/dom/media/webspeech/synth/test/file_speech_cancel.html @@ -21,40 +21,58 @@ https://bugzilla.mozilla.org/show_bug.cg <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 1150315 **/ -synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true); - var gotEndEvent = false; -var utterance = new SpeechSynthesisUtterance("Hello, world!"); +// A long utterance that we will interrupt. +var utterance = new SpeechSynthesisUtterance("Donec ac nunc feugiat, posuere " + + "mauris id, pharetra velit. Donec fermentum orci nunc, sit amet maximus" + + "dui tincidunt ut. Sed ultricies ac nisi a laoreet. Proin interdum," + + "libero maximus hendrerit posuere, lorem risus egestas nisl, a" + + "ultricies massa justo eu nisi. Duis mattis nibh a ligula tincidunt" + + "tincidunt non eu erat. Sed bibendum varius vulputate. Cras leo magna," + + "ornare ac posuere vel, luctus id metus. Mauris nec quam ac augue" + + "consectetur bibendum. Integer a commodo tortor. Duis semper dolor eu" + + "facilisis facilisis. Etiam venenatis turpis est, quis tincidunt velit" + + "suscipit a. Cras semper orci in sapien rhoncus bibendum. Suspendisse" + + "eu ex lobortis, finibus enim in, condimentum quam. Maecenas eget dui" + + "ipsum. Aliquam tortor leo, interdum eget congue ut, tempor id elit."); utterance.addEventListener('start', function(e) { + ok(true, 'start utterance 1'); speechSynthesis.cancel(); speechSynthesis.speak(utterance2); }); -var utterance2 = new SpeechSynthesisUtterance("Hello, world 2!"); +var utterance2 = new SpeechSynthesisUtterance("Proin ornare neque vitae " + + "risus mattis rutrum. Suspendisse a velit ut est convallis aliquet." + + "Nullam ante elit, malesuada vel luctus rutrum, ultricies nec libero." + + "Praesent eu iaculis orci. Sed nisl diam, sodales ac purus et," + + "volutpat interdum tortor. Nullam aliquam porta elit et maximus. Cras" + + "risus lectus, elementum vel sodales vel, ultricies eget lectus." + + "Curabitur velit lacus, mollis vel finibus et, molestie sit amet" + + "sapien. Proin vitae dolor ac augue posuere efficitur ac scelerisque" + + "diam. Nulla sed odio elit."); utterance2.addEventListener('start', function() { speechSynthesis.cancel(); speechSynthesis.speak(utterance3); }); utterance2.addEventListener('end', function(e) { gotEndEvent = true; }); var utterance3 = new SpeechSynthesisUtterance("Hello, world 3!"); utterance3.addEventListener('start', function() { - ok(gotEndEvent, "didn't get start event for this utterance") + ok(gotEndEvent, "didn't get start event for this utterance"); }); utterance3.addEventListener('end', function(e) { - synthCleanup(); SimpleTest.finish(); }); speechSynthesis.speak(utterance); ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet."); ok(speechSynthesis.pending, "speechSynthesis has an utterance queued."); </script>
--- a/dom/media/webspeech/synth/test/file_speech_queue.html +++ b/dom/media/webspeech/synth/test/file_speech_queue.html @@ -13,58 +13,64 @@ https://bugzilla.mozilla.org/show_bug.cg window.ok = parent.ok; </script> <script type="application/javascript" src="common.js"></script> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525444">Mozilla Bug 525444</a> <p id="display"></p> <div id="content" style="display: none"> - + </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 525444 **/ -var englishJamaican = synthAddVoice('TestSpeechServiceNoAudio', - 'Bob Marley', 'en-JM', true); -var englishBritish = synthAddVoice('TestSpeechServiceNoAudio', - 'Amy Winehouse', 'en-GB', true); -var englishCanadian = synthAddVoice('TestSpeechServiceNoAudio', - 'Leonard Cohen', 'en-CA', true); -var frenchCanadian = synthAddVoice('TestSpeechServiceNoAudio', - 'Celine Dion', 'fr-CA', true); -var spanishMexican = synthAddVoice('TestSpeechServiceNoAudio', - 'Julieta Venegas', 'es-MX', true); +// XXX: Rate and pitch are not tested. + +var langUriMap = {}; -synthSetDefault(englishBritish, true); +for (var voice of speechSynthesis.getVoices()) { + if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) { + continue; + } + langUriMap[voice.lang] = voice.voiceURI; + ok(true, voice.lang + ' ' + voice.voiceURI + ' ' + voice.default); + is(voice.default, voice.lang == 'en-JM', 'Only Jamaican voice should be default'); +} + +ok(langUriMap['en-JM'], 'No English-Jamaican voice'); +ok(langUriMap['en-GB'], 'No English-British voice'); +ok(langUriMap['en-CA'], 'No English-Canadian voice'); +ok(langUriMap['fr-CA'], 'No French-Canadian voice'); +ok(langUriMap['es-MX'], 'No Spanish-Mexican voice'); synthTestQueue( [[{text: "Hello, world."}, - { uri: englishBritish }], + { uri: langUriMap['en-JM'] }], [{text: "Bonjour tout le monde .", lang: "fr", rate: 0.5, pitch: 0.75}, - { uri: frenchCanadian, rate: 0.5, pitch: 0.75}], + { uri: langUriMap['fr-CA'], rate: 0.5, pitch: 0.75}], [{text: "How are you doing?", lang: "en-GB"}, - { rate: 1, pitch: 1, uri: englishBritish}], - [{text: "¡hasta mañana", lang: "es-ES"}, - { uri: spanishMexican }]], + { rate: 1, pitch: 1, uri: langUriMap['en-GB']}], + [{text: "¡hasta mañana!", lang: "es-MX"}, + { uri: langUriMap['es-MX'] }]], function () { - synthSetDefault(englishJamaican, true); - var test_data = [[{text: "I shot the sheriff."}, - { uri: englishJamaican }]]; + var test_data = []; var voices = speechSynthesis.getVoices(); - for (var i in voices) { - test_data.push([{text: "Hello world", voice: voices[i]}, - {uri: voices[i].voiceURI}]); + for (var voice of voices) { + if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) { + continue; + } + test_data.push([{text: "Hello world", voice: voice}, + {uri: voice.voiceURI}]); } synthTestQueue(test_data, function () { - synthCleanup(); SimpleTest.finish(); }); }); </script> </pre>
--- a/dom/media/webspeech/synth/test/file_speech_simple.html +++ b/dom/media/webspeech/synth/test/file_speech_simple.html @@ -21,33 +21,30 @@ https://bugzilla.mozilla.org/show_bug.cg <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 525444 **/ -synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true); - var gotStartEvent = false; var gotBoundaryEvent = false; var utterance = new SpeechSynthesisUtterance("Hello, world!"); utterance.addEventListener('start', function(e) { ok(speechSynthesis.speaking, "speechSynthesis is speaking."); ok(!speechSynthesis.pending, "speechSynthesis has no other utterances queued."); gotStartEvent = true; }); utterance.addEventListener('end', function(e) { ok(!speechSynthesis.speaking, "speechSynthesis is not speaking."); ok(!speechSynthesis.pending, "speechSynthesis has no other utterances queued."); ok(gotStartEvent, "Got 'start' event."); info('end ' + e.elapsedTime); - synthCleanup(); SimpleTest.finish(); }); speechSynthesis.speak(utterance); ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet."); ok(speechSynthesis.pending, "speechSynthesis has an utterance queued."); </script>
--- a/dom/media/webspeech/synth/test/mochitest.ini +++ b/dom/media/webspeech/synth/test/mochitest.ini @@ -1,19 +1,14 @@ [DEFAULT] -skip-if = e10s support-files = common.js file_setup.html file_speech_queue.html file_speech_simple.html file_speech_cancel.html file_indirect_service_events.html [test_setup.html] [test_speech_queue.html] -skip-if = buildapp == 'b2g' # b2g(Test timed out) [test_speech_simple.html] -skip-if = buildapp == 'b2g' # b2g(Test timed out) [test_speech_cancel.html] -skip-if = toolkit == 'gonk' # b2g(Test timed out) [test_indirect_service_events.html] -skip-if = toolkit == 'gonk' # b2g(Test timed out)
new file mode 100644 --- /dev/null +++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.h" +#include "nsFakeSynthServices.h" +#include "nsPrintfCString.h" +#include "nsIWeakReferenceUtils.h" +#include "SharedBuffer.h" +#include "nsISimpleEnumerator.h" + +#include "mozilla/dom/nsSynthVoiceRegistry.h" +#include "mozilla/dom/nsSpeechTask.h" + +#include "nsThreadUtils.h" +#include "prenv.h" +#include "mozilla/Preferences.h" +#include "mozilla/DebugOnly.h" + +#define CHANNELS 1 +#define SAMPLERATE 1600 + +namespace mozilla { +namespace dom { + +StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton; + +enum VoiceFlags +{ + eSuppressEvents = 1, + eSuppressEnd = 2 +}; + +struct VoiceDetails +{ + const char* uri; + const char* name; + const char* lang; + bool defaultVoice; + uint32_t flags; +}; + +static const VoiceDetails sDirectVoices[] = { + {"urn:moz-tts:fake-direct:bob", "Bob Marley", "en-JM", true, 0}, + {"urn:moz-tts:fake-direct:amy", "Amy Winehouse", "en-GB", false, 0}, + {"urn:moz-tts:fake-direct:lenny", "Leonard Cohen", "en-CA", false, 0}, + {"urn:moz-tts:fake-direct:celine", "Celine Dion", "fr-CA", false, 0}, + {"urn:moz-tts:fake-direct:julie", "Julieta Venegas", "es-MX", false, }, +}; + +static const VoiceDetails sIndirectVoices[] = { + {"urn:moz-tts:fake-indirect:zanetta", "Zanetta Farussi", "it-IT", false, 0}, + {"urn:moz-tts:fake-indirect:margherita", "Margherita Durastanti", "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd}, + {"urn:moz-tts:fake-indirect:teresa", "Teresa Cornelys", "it-IT-noend", false, eSuppressEnd}, +}; + +// FakeSynthCallback +class FakeSynthCallback : public nsISpeechTaskCallback +{ +public: + explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) { } + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback, nsISpeechTaskCallback) + + NS_IMETHOD OnPause() + { + if (mTask) { + mTask->DispatchPause(1.5, 1); + } + + return NS_OK; + } + + NS_IMETHOD OnResume() + { + if (mTask) { + mTask->DispatchResume(1.5, 1); + } + + return NS_OK; + } + + NS_IMETHOD OnCancel() + { + if (mTask) { + mTask->DispatchEnd(1.5, 1); + } + + return NS_OK; + } + +private: + virtual ~FakeSynthCallback() { } + + nsCOMPtr<nsISpeechTask> mTask; +}; + +NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask); + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback) + NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback) +NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback) + +// FakeDirectAudioSynth + +class FakeDirectAudioSynth : public nsISpeechService +{ + +public: + FakeDirectAudioSynth() { } + + NS_DECL_ISUPPORTS + NS_DECL_NSISPEECHSERVICE + +private: + virtual ~FakeDirectAudioSynth() { } +}; + +NS_IMPL_ISUPPORTS(FakeDirectAudioSynth, nsISpeechService) + +NS_IMETHODIMP +FakeDirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri, + float aVolume, float aRate, float aPitch, + nsISpeechTask* aTask) +{ + class Runnable final : public nsRunnable + { + public: + Runnable(nsISpeechTask* aTask, const nsAString& aText) : + mTask(aTask), mText(aText) + { + } + + NS_IMETHOD Run() override + { + nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback(nullptr); + mTask->Setup(cb, CHANNELS, SAMPLERATE, 2); + + // Just an arbitrary multiplier. Pretend that each character is + // synthesized to 40 frames. + uint32_t frames_length = 40 * mText.Length(); + nsAutoArrayPtr<int16_t> frames(new int16_t[frames_length]()); + mTask->SendAudioNative(frames, frames_length); + + mTask->SendAudioNative(nullptr, 0); + + return NS_OK; + } + + private: + nsCOMPtr<nsISpeechTask> mTask; + nsString mText; + }; + + nsCOMPtr<nsIRunnable> runnable = new Runnable(aTask, aText); + NS_DispatchToMainThread(runnable); + return NS_OK; +} + +NS_IMETHODIMP +FakeDirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType) +{ + *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO; + return NS_OK; +} + +// FakeDirectAudioSynth + +class FakeIndirectAudioSynth : public nsISpeechService +{ + +public: + FakeIndirectAudioSynth() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISPEECHSERVICE + +private: + virtual ~FakeIndirectAudioSynth() { } +}; + +NS_IMPL_ISUPPORTS(FakeIndirectAudioSynth, nsISpeechService) + +NS_IMETHODIMP +FakeIndirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri, + float aVolume, float aRate, float aPitch, + nsISpeechTask* aTask) +{ + class DispatchStart final : public nsRunnable + { + public: + explicit DispatchStart(nsISpeechTask* aTask) : + mTask(aTask) + { + } + + NS_IMETHOD Run() override + { + mTask->DispatchStart(); + + return NS_OK; + } + + private: + nsCOMPtr<nsISpeechTask> mTask; + }; + + class DispatchEnd final : public nsRunnable + { + public: + DispatchEnd(nsISpeechTask* aTask, const nsAString& aText) : + mTask(aTask), mText(aText) + { + } + + NS_IMETHOD Run() override + { + mTask->DispatchEnd(mText.Length()/2, mText.Length()); + + return NS_OK; + } + + private: + nsCOMPtr<nsISpeechTask> mTask; + nsString mText; + }; + + uint32_t flags = 0; + for (uint32_t i = 0; i < ArrayLength(sIndirectVoices); i++) { + if (aUri.EqualsASCII(sIndirectVoices[i].uri)) { + flags = sIndirectVoices[i].flags; + } + } + + nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback( + (flags & eSuppressEvents) ? nullptr : aTask); + + aTask->Setup(cb, 0, 0, 0); + + nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask); + NS_DispatchToMainThread(runnable); + + if ((flags & eSuppressEnd) == 0) { + runnable = new DispatchEnd(aTask, aText); + NS_DispatchToMainThread(runnable); + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeIndirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType) +{ + *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO; + return NS_OK; +} + +// nsFakeSynthService + +NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsFakeSynthServices) +NS_IMPL_RELEASE(nsFakeSynthServices) + +nsFakeSynthServices::nsFakeSynthServices() +{ +} + +nsFakeSynthServices::~nsFakeSynthServices() +{ +} + +static void +AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, uint32_t aLength) +{ + nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance(); + for (uint32_t i = 0; i < aLength; i++) { + NS_ConvertUTF8toUTF16 name(aVoices[i].name); + NS_ConvertUTF8toUTF16 uri(aVoices[i].uri); + NS_ConvertUTF8toUTF16 lang(aVoices[i].lang); + registry->AddVoice(aService, uri, name, lang, true); + if (aVoices[i].defaultVoice) { + registry->SetDefaultVoice(uri, true); + } + } +} + +void +nsFakeSynthServices::Init() +{ + mDirectService = new FakeDirectAudioSynth(); + AddVoices(mDirectService, sDirectVoices, ArrayLength(sDirectVoices)); + + mIndirectService = new FakeIndirectAudioSynth(); + AddVoices(mIndirectService, sIndirectVoices, ArrayLength(sIndirectVoices)); +} + +// nsIObserver + +NS_IMETHODIMP +nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED); + + if (Preferences::GetBool("media.webspeech.synth.test")) { + Init(); + } + + return NS_OK; +} + +// static methods + +nsFakeSynthServices* +nsFakeSynthServices::GetInstance() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (XRE_GetProcessType() != GeckoProcessType_Default) { + MOZ_ASSERT(false, "nsFakeSynthServices can only be started on main gecko process"); + return nullptr; + } + + if (!sSingleton) { + sSingleton = new nsFakeSynthServices(); + } + + return sSingleton; +} + +already_AddRefed<nsFakeSynthServices> +nsFakeSynthServices::GetInstanceForService() +{ + nsRefPtr<nsFakeSynthServices> picoService = GetInstance(); + return picoService.forget(); +} + +void +nsFakeSynthServices::Shutdown() +{ + if (!sSingleton) { + return; + } + + sSingleton = nullptr; +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFakeSynthServices_h +#define nsFakeSynthServices_h + +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "nsIObserver.h" +#include "nsIThread.h" +#include "nsISpeechService.h" +#include "nsRefPtrHashtable.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Monitor.h" + +namespace mozilla { +namespace dom { + +class nsFakeSynthServices : public nsIObserver +{ + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsFakeSynthServices(); + + static nsFakeSynthServices* GetInstance(); + + static already_AddRefed<nsFakeSynthServices> GetInstanceForService(); + + static void Shutdown(); + +private: + + virtual ~nsFakeSynthServices(); + + void Init(); + + nsCOMPtr<nsISpeechService> mDirectService; + + nsCOMPtr<nsISpeechService> mIndirectService; + + static StaticRefPtr<nsFakeSynthServices> sSingleton; +}; + +} // namespace dom +} // namespace mozilla + +#endif
--- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -819,20 +819,25 @@ nsGIFDecoder2::WriteInternal(const char* case gif_skip_block: GETN(1, gif_consume_block); break; case gif_control_extension: mGIFStruct.is_transparent = *q & 0x1; mGIFStruct.tpixel = q[3]; mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; - // Some specs say 3rd bit (value 4), other specs say value 3 - // Let's choose 3 (the more popular) + if (mGIFStruct.disposal_method == 4) { + // Some specs say 3rd bit (value 4), other specs say value 3. + // Let's choose 3 (the more popular). mGIFStruct.disposal_method = 3; + } else if (mGIFStruct.disposal_method > 4) { + // This GIF is using a disposal method which is undefined in the spec. + // Treat it as DisposalMethod::NOT_SPECIFIED. + mGIFStruct.disposal_method = 0; } { DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method); if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) { // We may have to display the background under this image during // animation playback, so we regard it as transparent.
--- a/image/src/DecodePool.cpp +++ b/image/src/DecodePool.cpp @@ -3,20 +3,22 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DecodePool.h" #include <algorithm> #include "mozilla/ClearOnShutdown.h" +#include "mozilla/Monitor.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsIObserverService.h" #include "nsIThreadPool.h" +#include "nsThreadManager.h" #include "nsThreadUtils.h" #include "nsXPCOMCIDInternal.h" #include "prsystem.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif @@ -102,79 +104,199 @@ public: private: explicit NotifyDecodeCompleteWorker(Decoder* aDecoder) : mDecoder(aDecoder) { } nsRefPtr<Decoder> mDecoder; }; -class DecodeWorker : public nsRunnable -{ -public: - explicit DecodeWorker(Decoder* aDecoder) - : mDecoder(aDecoder) - { - MOZ_ASSERT(mDecoder); - } - - NS_IMETHOD Run() override - { - MOZ_ASSERT(!NS_IsMainThread()); - DecodePool::Singleton()->Decode(mDecoder); - return NS_OK; - } - -private: - nsRefPtr<Decoder> mDecoder; -}; - #ifdef MOZ_NUWA_PROCESS -class DecodePoolNuwaListener final : public nsIThreadPoolListener -{ -public: - NS_DECL_THREADSAFE_ISUPPORTS - - NS_IMETHODIMP OnThreadCreated() - { - if (IsNuwaProcess()) { - NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr); - } - return NS_OK; - } - - NS_IMETHODIMP OnThreadShuttingDown() { return NS_OK; } - -private: - ~DecodePoolNuwaListener() { } -}; - -NS_IMPL_ISUPPORTS(DecodePoolNuwaListener, nsIThreadPoolListener) - class RegisterDecodeIOThreadWithNuwaRunnable : public nsRunnable { public: NS_IMETHOD Run() { NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr); return NS_OK; } }; + #endif // MOZ_NUWA_PROCESS /////////////////////////////////////////////////////////////////////////////// // DecodePool implementation. /////////////////////////////////////////////////////////////////////////////// /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton; NS_IMPL_ISUPPORTS(DecodePool, nsIObserver) +struct Work +{ + enum class Type { + DECODE, + SHUTDOWN + } mType; + + nsRefPtr<Decoder> mDecoder; +}; + +class DecodePoolImpl +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl) + + DecodePoolImpl() + : mMonitor("DecodePoolImpl") + , mShuttingDown(false) + { } + + /// Initialize the current thread for use by the decode pool. + void InitCurrentThread() + { + MOZ_ASSERT(!NS_IsMainThread()); + + mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder")); + +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess()) { + NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr); + } +#endif // MOZ_NUWA_PROCESS + } + + /// Shut down the provided decode pool thread. + static void ShutdownThread(nsIThread* aThisThread) + { + // Threads have to be shut down from another thread, so we'll ask the + // main thread to do it for us. + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableMethod(aThisThread, &nsIThread::Shutdown); + NS_DispatchToMainThread(runnable); + } + + /** + * Requests shutdown. New work items will be dropped on the floor, and all + * decode pool threads will be shut down once existing work items have been + * processed. + */ + void RequestShutdown() + { + MonitorAutoLock lock(mMonitor); + mShuttingDown = true; + mMonitor.NotifyAll(); + } + + /// Pushes a new decode work item. + void PushWork(Decoder* aDecoder) + { + nsRefPtr<Decoder> decoder(aDecoder); + + MonitorAutoLock lock(mMonitor); + + if (mShuttingDown) { + // Drop any new work on the floor if we're shutting down. + return; + } + + mQueue.AppendElement(Move(decoder)); + mMonitor.Notify(); + } + + /// Pops a new work item, blocking if necessary. + Work PopWork() + { + Work work; + + MonitorAutoLock lock(mMonitor); + + do { + if (!mQueue.IsEmpty()) { + // XXX(seth): This is NOT efficient, obviously, since we're removing an + // element from the front of the array. However, it's not worth + // implementing something better right now, because we are replacing + // this FIFO behavior with LIFO behavior very soon. + work.mType = Work::Type::DECODE; + work.mDecoder = mQueue.ElementAt(0); + mQueue.RemoveElementAt(0); + +#ifdef MOZ_NUWA_PROCESS + nsThreadManager::get()->SetThreadWorking(); +#endif // MOZ_NUWA_PROCESS + + return work; + } + + if (mShuttingDown) { + work.mType = Work::Type::SHUTDOWN; + return work; + } + +#ifdef MOZ_NUWA_PROCESS + nsThreadManager::get()->SetThreadIdle(nullptr); +#endif // MOZ_NUWA_PROCESS + + // Nothing to do; block until some work is available. + mMonitor.Wait(); + } while (true); + } + +private: + ~DecodePoolImpl() { } + + nsThreadPoolNaming mThreadNaming; + + // mMonitor guards mQueue and mShuttingDown. + Monitor mMonitor; + nsTArray<nsRefPtr<Decoder>> mQueue; + bool mShuttingDown; +}; + +class DecodePoolWorker : public nsRunnable +{ +public: + explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(!NS_IsMainThread()); + + mImpl->InitCurrentThread(); + + nsCOMPtr<nsIThread> thisThread; + nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thisThread)); + + do { + Work work = mImpl->PopWork(); + switch (work.mType) { + case Work::Type::DECODE: + DecodePool::Singleton()->Decode(work.mDecoder); + break; + + case Work::Type::SHUTDOWN: + DecodePoolImpl::ShutdownThread(thisThread); + return NS_OK; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown work type"); + } + } while (true); + + MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN"); + return NS_OK; + } + +private: + nsRefPtr<DecodePoolImpl> mImpl; +}; + /* static */ void DecodePool::Initialize() { MOZ_ASSERT(NS_IsMainThread()); DecodePool::Singleton(); } /* static */ DecodePool* @@ -185,40 +307,37 @@ DecodePool::Singleton() sSingleton = new DecodePool(); ClearOnShutdown(&sSingleton); } return sSingleton; } DecodePool::DecodePool() - : mMutex("image::DecodePool") + : mImpl(new DecodePoolImpl) + , mMutex("image::DecodePool") { - // Initialize the thread pool. - mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); - MOZ_RELEASE_ASSERT(mThreadPool, - "Should succeed in creating image decoding thread pool"); - - mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder")); + // Determine the number of threads we want. int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit(); uint32_t limit; if (prefLimit <= 0) { limit = max(PR_GetNumberOfProcessors(), 2) - 1; } else { limit = static_cast<uint32_t>(prefLimit); } - mThreadPool->SetThreadLimit(limit); - mThreadPool->SetIdleThreadLimit(limit); - -#ifdef MOZ_NUWA_PROCESS - if (IsNuwaProcess()) { - mThreadPool->SetListener(new DecodePoolNuwaListener()); + // Initialize the thread pool. + for (uint32_t i = 0 ; i < limit ; ++i) { + nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl); + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), worker); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread, + "Should successfully create image decoding threads"); + mThreads.AppendElement(Move(thread)); } -#endif // Initialize the I/O thread. nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread)); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread, "Should successfully create image I/O thread"); #ifdef MOZ_NUWA_PROCESS nsCOMPtr<nsIRunnable> worker = new RegisterDecodeIOThreadWithNuwaRunnable(); @@ -238,49 +357,44 @@ DecodePool::~DecodePool() MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!"); } NS_IMETHODIMP DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) { MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic"); - nsCOMPtr<nsIThreadPool> threadPool; + nsCOMArray<nsIThread> threads; nsCOMPtr<nsIThread> ioThread; { MutexAutoLock lock(mMutex); - threadPool.swap(mThreadPool); + threads.AppendElements(mThreads); + mThreads.Clear(); ioThread.swap(mIOThread); } - if (threadPool) { - threadPool->Shutdown(); + mImpl->RequestShutdown(); + + for (int32_t i = 0 ; i < threads.Count() ; ++i) { + threads[i]->Shutdown(); } if (ioThread) { ioThread->Shutdown(); } return NS_OK; } void DecodePool::AsyncDecode(Decoder* aDecoder) { MOZ_ASSERT(aDecoder); - - nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder); - - // Dispatch to the thread pool if it exists. If it doesn't, we're currently - // shutting down, so it's OK to just drop the job on the floor. - MutexAutoLock threadPoolLock(mMutex); - if (mThreadPool) { - mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL); - } + mImpl->PushWork(aDecoder); } void DecodePool::SyncDecodeIfSmall(Decoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aDecoder); @@ -295,39 +409,23 @@ DecodePool::SyncDecodeIfSmall(Decoder* a void DecodePool::SyncDecodeIfPossible(Decoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); Decode(aDecoder); } already_AddRefed<nsIEventTarget> -DecodePool::GetEventTarget() -{ - MutexAutoLock threadPoolLock(mMutex); - nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool); - return target.forget(); -} - -already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() { MutexAutoLock threadPoolLock(mMutex); nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread); return target.forget(); } -already_AddRefed<nsIRunnable> -DecodePool::CreateDecodeWorker(Decoder* aDecoder) -{ - MOZ_ASSERT(aDecoder); - nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder); - return worker.forget(); -} - void DecodePool::Decode(Decoder* aDecoder) { MOZ_ASSERT(aDecoder); nsresult rv = aDecoder->Decode(); if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
--- a/image/src/DecodePool.h +++ b/image/src/DecodePool.h @@ -7,27 +7,30 @@ * DecodePool manages the threads used for decoding raster images. */ #ifndef mozilla_image_src_DecodePool_h #define mozilla_image_src_DecodePool_h #include "mozilla/Mutex.h" #include "mozilla/StaticPtr.h" +#include "nsCOMArray.h" #include "nsCOMPtr.h" #include "nsIEventTarget.h" #include "nsIObserver.h" +#include "nsRefPtr.h" class nsIThread; class nsIThreadPool; namespace mozilla { namespace image { class Decoder; +class DecodePoolImpl; /** * DecodePool is a singleton class that manages decoding of raster images. It * owns a pool of image decoding threads that are used for asynchronous * decoding. * * DecodePool allows callers to run a decoder, handling management of the * decoder's lifecycle and whether it executes on the main thread, @@ -59,56 +62,40 @@ public: /** * Run aDecoder synchronously if at all possible. If it can't complete * synchronously because the source data isn't complete, asynchronously decode * the rest. */ void SyncDecodeIfPossible(Decoder* aDecoder); /** - * Returns an event target interface to the DecodePool's underlying thread - * pool. Callers can use this event target to submit work to the image - * decoding thread pool. - * - * @return An nsIEventTarget interface to the thread pool. - */ - already_AddRefed<nsIEventTarget> GetEventTarget(); - - /** * Returns an event target interface to the DecodePool's I/O thread. Callers * who want to deliver data to workers on the DecodePool can use this event * target. * * @return An nsIEventTarget interface to the thread pool's I/O thread. */ already_AddRefed<nsIEventTarget> GetIOEventTarget(); - /** - * Creates a worker which can be used to attempt further decoding using the - * provided decoder. - * - * @return The new worker, which should be posted to the event target returned - * by GetEventTarget. - */ - already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder); - private: - friend class DecodeWorker; + friend class DecodePoolWorker; DecodePool(); virtual ~DecodePool(); void Decode(Decoder* aDecoder); void NotifyDecodeComplete(Decoder* aDecoder); void NotifyProgress(Decoder* aDecoder); static StaticRefPtr<DecodePool> sSingleton; - // mMutex protects mThreadPool and mIOThread. + nsRefPtr<DecodePoolImpl> mImpl; + + // mMutex protects mThreads and mIOThread. Mutex mMutex; - nsCOMPtr<nsIThreadPool> mThreadPool; + nsCOMArray<nsIThread> mThreads; nsCOMPtr<nsIThread> mIOThread; }; } // namespace image } // namespace mozilla #endif // mozilla_image_src_DecodePool_h
--- a/image/src/Decoder.cpp +++ b/image/src/Decoder.cpp @@ -161,25 +161,17 @@ Decoder::Decode() return HasError() ? NS_ERROR_FAILURE : NS_OK; } void Decoder::Resume() { DecodePool* decodePool = DecodePool::Singleton(); MOZ_ASSERT(decodePool); - - nsCOMPtr<nsIEventTarget> target = decodePool->GetEventTarget(); - if (MOZ_UNLIKELY(!target)) { - // We're shutting down and the DecodePool's thread pool has been destroyed. - return; - } - - nsCOMPtr<nsIRunnable> worker = decodePool->CreateDecodeWorker(this); - target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL); + decodePool->AsyncDecode(this); } bool Decoder::ShouldSyncDecode(size_t aByteLimit) { MOZ_ASSERT(aByteLimit > 0); MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
--- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -942,16 +942,20 @@ RasterImage::OnAddedFrame(uint32_t aNewF NS_DispatchToMainThread(runnable); return; } MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) || mFrameCount < aNewFrameCount, "Frame count running backwards"); + if (mError) { + return; // We're in an error state, possibly due to OOM. Bail. + } + if (aNewFrameCount > mFrameCount) { mFrameCount = aNewFrameCount; if (aNewFrameCount == 2) { // We're becoming animated, so initialize animation stuff. MOZ_ASSERT(!mAnim, "Already have animation state?"); mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
--- a/image/src/moz.build +++ b/image/src/moz.build @@ -63,12 +63,14 @@ LOCAL_INCLUDES += [ # Access to Skia headers for Downscaler '/gfx/2d', # We need to instantiate the decoders '/image/decoders', # Because VectorImage.cpp includes nsSVGUtils.h and nsSVGEffects.h '/layout/svg', # For URI-related functionality '/netwerk/base', + # DecodePool uses thread-related facilities. + '/xpcom/threads', ] # Because imgFrame.cpp includes "cairo.h" CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
--- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -42,8 +42,13 @@ load truncated-second-frame.png # bug 86 # after we've gotten our size. load multiple-png-hassize.ico # Bug 856615 # Asserts in the debug build load 856616.gif skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg + +# Bug 1160801: Ensure that we handle invalid disposal types. +load invalid-disposal-method-1.gif +load invalid-disposal-method-2.gif +load invalid-disposal-method-3.gif
new file mode 100644 index 0000000000000000000000000000000000000000..30c61de188b9c63ea179c008c5171efad638f853 GIT binary patch literal 167 zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!o|SAp!iSFxhOTUBsE2$JhLQ2!QIn0 zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F SD+AC#29SZsdRvIpYYhO5fk;^Z
new file mode 100644 index 0000000000000000000000000000000000000000..66158d81a90b670d28b258a22f87f037542122f0 GIT binary patch literal 167 zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!NtJ9p!iSFxhOTUBsE2$JhLQ2!QIn0 zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F SD+AC#29SZsdRvIpYYhO7tw>z}
new file mode 100644 index 0000000000000000000000000000000000000000..0da0723773e1b6c4f3d4e9df6d47ebe753f6c087 GIT binary patch literal 167 zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!^ObBp!iSFxhOTUBsE2$JhLQ2!QIn0 zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F SD+AC#29SZsdRvIpYYhO9*+^jk
--- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -27,8 +27,14 @@ function MapForEach(callbackfn, thisArg while (true) { var result = callFunction(std_Map_iterator_next, entries); if (result.done) break; var entry = result.value; callFunction(callbackfn, thisArg, entry[1], entry[0], M); } } + +// ES6 final draft 23.1.2.2. +function MapSpecies() { + // Step 1. + return this; +}
--- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -1045,42 +1045,50 @@ const JSFunctionSpec MapObject::methods[ JS_FN("delete", delete_, 1, 0), JS_FN("keys", keys, 0, 0), JS_FN("values", values, 0, 0), JS_FN("clear", clear, 0, 0), JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0), JS_FS_END }; +const JSPropertySpec MapObject::staticProperties[] = { + JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0), + JS_PS_END +}; + static JSObject* InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct, - const JSPropertySpec* properties, const JSFunctionSpec* methods) + const JSPropertySpec* properties, const JSFunctionSpec* methods, + const JSPropertySpec* staticProperties) { RootedNativeObject proto(cx, global->createBlankPrototype(cx, clasp)); if (!proto) return nullptr; proto->setPrivate(nullptr); Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0)); if (!ctor || + !JS_DefineProperties(cx, ctor, staticProperties) || !LinkConstructorAndPrototype(cx, ctor, proto) || !DefinePropertiesAndFunctions(cx, proto, properties, methods) || !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto)) { return nullptr; } return proto; } JSObject* MapObject::initClass(JSContext* cx, JSObject* obj) { Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); RootedObject proto(cx, - InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods)); + InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods, + staticProperties)); if (proto) { // Define the "entries" method. JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0); if (!fun) return nullptr; // Define its alias. RootedValue funval(cx, ObjectValue(*fun)); @@ -1777,22 +1785,28 @@ const JSFunctionSpec SetObject::methods[ JS_FN("add", add, 1, 0), JS_FN("delete", delete_, 1, 0), JS_FN("entries", entries, 0, 0), JS_FN("clear", clear, 0, 0), JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0), JS_FS_END }; +const JSPropertySpec SetObject::staticProperties[] = { + JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0), + JS_PS_END +}; + JSObject* SetObject::initClass(JSContext* cx, JSObject* obj) { Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); RootedObject proto(cx, - InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods)); + InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods, + staticProperties)); if (proto) { // Define the "values" method. JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0); if (!fun) return nullptr; // Define its aliases. RootedValue funval(cx, ObjectValue(*fun));
--- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -103,16 +103,17 @@ class MapObject : public NativeObject { static bool has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval); static bool set(JSContext* cx, HandleObject obj, HandleValue key, HandleValue val); static bool clear(JSContext* cx, HandleObject obj); static bool iterator(JSContext* cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter); private: static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; + static const JSPropertySpec staticProperties[]; ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); } static ValueMap & extract(HandleObject o); static ValueMap & extract(CallReceiver call); static void mark(JSTracer* trc, JSObject* obj); static void finalize(FreeOp* fop, JSObject* obj); static bool construct(JSContext* cx, unsigned argc, Value* vp); static bool is(HandleValue v); @@ -148,16 +149,17 @@ class SetObject : public NativeObject { static bool values(JSContext* cx, unsigned argc, Value* vp); static bool add(JSContext* cx, HandleObject obj, HandleValue key); static bool has(JSContext* cx, unsigned argc, Value* vp); static SetObject* create(JSContext* cx); private: static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; + static const JSPropertySpec staticProperties[]; ValueSet* getData() { return static_cast<ValueSet*>(getPrivate()); } static ValueSet & extract(CallReceiver call); static void mark(JSTracer* trc, JSObject* obj); static void finalize(FreeOp* fop, JSObject* obj); static bool construct(JSContext* cx, unsigned argc, Value* vp); static bool is(HandleValue v);
--- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -27,8 +27,14 @@ function SetForEach(callbackfn, thisArg while (true) { var result = callFunction(std_Set_iterator_next, values); if (result.done) break; var value = result.value; callFunction(callbackfn, thisArg, value, value, S); } } + +// ES6 final draft 23.2.2.2. +function SetSpecies() { + // Step 1. + return this; +}
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2098,16 +2098,21 @@ inline int CheckIsSetterOp(JSSetterOp op { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) } }, \ JSNATIVE_WRAPPER(nullptr) } #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \ {name, \ uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER), \ { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) }, \ { nullptr, JS_CAST_STRING_TO(setterName, const JSJitInfo*) } } #define JS_PS_END { nullptr, 0, JSNATIVE_WRAPPER(nullptr), JSNATIVE_WRAPPER(nullptr) } +#define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags) \ + {reinterpret_cast<const char*>(uint32_t(::JS::SymbolCode::symbol) + 1), \ + uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER), \ + { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) } }, \ + JSNATIVE_WRAPPER(nullptr) } /* * To define a native function, set call to a JSNativeWrapper. To define a * self-hosted function, set selfHostedName to the name of a function * compiled during JSRuntime::initSelfHosting. */ struct JSFunctionSpec { const char* name; @@ -4350,22 +4355,23 @@ GetSymbolFor(JSContext* cx, HandleString */ JS_PUBLIC_API(JSString*) GetSymbolDescription(HandleSymbol symbol); /* Well-known symbols. */ enum class SymbolCode : uint32_t { iterator, // Symbol.iterator match, // Symbol.match + species, // Symbol.species InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor() UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol() }; /* For use in loops that iterate over the well-known symbols. */ -const size_t WellKnownSymbolLimit = 2; +const size_t WellKnownSymbolLimit = 3; /* * Return the SymbolCode telling what sort of symbol `symbol` is. * * A symbol's SymbolCode never changes once it is created. */ JS_PUBLIC_API(SymbolCode) GetSymbolCode(Handle<Symbol*> symbol);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Symbol/species.js @@ -0,0 +1,22 @@ +var BUGNUMBER = 1131043; +var summary = "Implement @@species getter for builtin types"; + +print(BUGNUMBER + ": " + summary); + +for (var C of [Map, Set]) { + assertEq(C[Symbol.species], C); +} + +for (C of [Map, Set]) { + var desc = Object.getOwnPropertyDescriptor(C, Symbol.species); + assertDeepEq(Object.keys(desc).sort(), ["configurable", "enumerable", "get", "set"]); + assertEq(desc.set, undefined); + assertEq(desc.enumerable, false); + assertEq(desc.configurable, true); + assertEq(desc.get.apply(null), null); + assertEq(desc.get.apply(undefined), undefined); + assertEq(desc.get.apply(42), 42); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/well-known.js +++ b/js/src/tests/ecma_6/Symbol/well-known.js @@ -1,14 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/licenses/publicdomain/ */ var names = [ "iterator", "match", + "species", ]; for (var name of names) { // Well-known symbols exist. assertEq(typeof Symbol[name], "symbol"); // They are never in the registry. assertEq(Symbol[name] !== Symbol.for("Symbol." + name), true);
--- a/js/src/tests/ecma_6/TypedArray/slice.js +++ b/js/src/tests/ecma_6/TypedArray/slice.js @@ -63,15 +63,15 @@ for (var constructor of constructors) { }, TypeError, "Assert that we have an invalid constructor"); // If obj.constructor[@@species] is undefined or null -- which it has to be // if we don't implement @@species -- then the default constructor is used. var mathConstructor = new constructor(8); mathConstructor.constructor = Math.sin; assertDeepEq(mathConstructor.slice(4), new constructor(4)); - assertEq("species" in Symbol, false, - "you've implemented @@species -- add real tests here!"); + assertEq(Symbol.species in Int8Array, false, + "you've implemented %TypedArray%[@@species] -- add real tests here!"); } if (typeof reportCompare === "function") reportCompare(true, true);
--- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -243,20 +243,22 @@ macro(number, number, "number") \ macro(boolean, boolean, "boolean") \ macro(null, null, "null") \ macro(symbol, symbol, "symbol") \ /* Well-known atom names must be continuous and ordered, matching \ * enum JS::SymbolCode in jsapi.h. */ \ macro(iterator, iterator, "iterator") \ macro(match, match, "match") \ + macro(species, species, "species") \ /* Same goes for the descriptions of the well-known symbols. */ \ macro(Symbol_create, Symbol_create, "Symbol.create") \ macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \ macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \ macro(Symbol_isRegExp, Symbol_isRegExp, "Symbol.isRegExp") \ macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \ macro(Symbol_match, Symbol_match, "Symbol.match") \ + macro(Symbol_species, Symbol_species, "Symbol.species") \ macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \ macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \ macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \ #endif /* vm_CommonPropertyNames_h */
--- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -386,16 +386,24 @@ GlobalObject::initSelfHostingBuiltins(JS RootedValue std_iterator(cx); std_iterator.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::iterator)); if (!JS_DefineProperty(cx, global, "std_iterator", std_iterator, JSPROP_PERMANENT | JSPROP_READONLY)) { return false; } + RootedValue std_species(cx); + std_species.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::species)); + if (!JS_DefineProperty(cx, global, "std_species", std_species, + JSPROP_PERMANENT | JSPROP_READONLY)) + { + return false; + } + return InitBareBuiltinCtor(cx, global, JSProto_Array) && InitBareBuiltinCtor(cx, global, JSProto_TypedArray) && InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) && InitBareBuiltinCtor(cx, global, JSProto_Uint32Array) && InitBareWeakMapCtor(cx, global) && initStopIterationClass(cx, global) && InitSelfHostingCollectionIteratorFunctions(cx, global) && JS_DefineFunctions(cx, global, builtins);
--- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -425,16 +425,17 @@ namespace js { * * Well-known symbols are never GC'd. The description() of each well-known * symbol is a permanent atom. */ struct WellKnownSymbols { js::ImmutableSymbolPtr iterator; js::ImmutableSymbolPtr match; + js::ImmutableSymbolPtr species; const ImmutableSymbolPtr& get(size_t u) const { MOZ_ASSERT(u < JS::WellKnownSymbolLimit); const ImmutableSymbolPtr* symbols = reinterpret_cast<const ImmutableSymbolPtr*>(this); return symbols[u]; } const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -8051,42 +8051,35 @@ PresShell::HandleEventInternal(WidgetEve // XXX How about IME events and input events for plugins? if (aEvent->mFlags.mIsTrusted) { switch (aEvent->message) { case NS_KEY_PRESS: case NS_KEY_DOWN: case NS_KEY_UP: { nsIDocument* doc = GetCurrentEventContent() ? mCurrentEventContent->OwnerDoc() : nullptr; - nsIDocument* fullscreenAncestor = nullptr; auto keyCode = aEvent->AsKeyboardEvent()->keyCode; if (keyCode == NS_VK_ESCAPE) { - if ((fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(doc))) { + nsIDocument* root = nsContentUtils::GetRootDocument(doc); + if (root && root->IsFullScreenDoc()) { // Prevent default action on ESC key press when exiting // DOM fullscreen mode. This prevents the browser ESC key // handler from stopping all loads in the document, which // would cause <video> loads to stop. aEvent->mFlags.mDefaultPrevented = true; aEvent->mFlags.mOnlyChromeDispatch = true; // The event listeners in chrome can prevent this ESC behavior by // calling prevent default on the preceding keydown/press events. if (!mIsLastChromeOnlyEscapeKeyConsumed && aEvent->message == NS_KEY_UP) { // ESC key released while in DOM fullscreen mode. - // If fullscreen is running in content-only mode, exit the target - // doctree branch from fullscreen, otherwise fully exit all - // browser windows and documents from fullscreen mode. - // Note: in the content-only fullscreen case, we pass the - // fullscreenAncestor since |doc| may not actually be fullscreen - // here, and ExitFullscreen() has no affect when passed a - // non-fullscreen document. - nsIDocument::ExitFullscreen( - nsContentUtils::IsFullscreenApiContentOnly() ? fullscreenAncestor : nullptr, - /* async */ true); + // Fully exit all browser windows and documents from + // fullscreen mode. + nsIDocument::ExitFullscreen(nullptr, /* async */ true); } } nsCOMPtr<nsIDocument> pointerLockedDoc = do_QueryReferent(EventStateManager::sPointerLockedDoc); if (!mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) { aEvent->mFlags.mDefaultPrevented = true; aEvent->mFlags.mOnlyChromeDispatch = true; if (aEvent->message == NS_KEY_UP) {
--- a/layout/generic/WritingModes.h +++ b/layout/generic/WritingModes.h @@ -25,16 +25,20 @@ // such methods should only be used by other methods that have already checked // the writing modes.) #define CHECK_WRITING_MODE(param) \ NS_ASSERTION(param == GetWritingMode(), "writing-mode mismatch") namespace mozilla { +namespace widget { +struct IMENotification; +} // namespace widget + // Physical axis constants. enum PhysicalAxis { eAxisVertical = 0x0, eAxisHorizontal = 0x1 }; inline bool IsInline(LogicalSide aSide) { return aSide & 0x2; } inline bool IsBlock(LogicalSide aSide) { return !IsInline(aSide); } @@ -470,16 +474,20 @@ public: private: friend class LogicalPoint; friend class LogicalSize; friend class LogicalMargin; friend class LogicalRect; friend struct IPC::ParamTraits<WritingMode>; + // IMENotification cannot store this class directly since this has some + // constructors. Therefore, it stores mWritingMode and recreate the + // instance from it. + friend struct widget::IMENotification; /** * Return a WritingMode representing an unknown value. */ static inline WritingMode Unknown() { return WritingMode(eUnknownWritingMode); }
--- a/layout/generic/test/chrome.ini +++ b/layout/generic/test/chrome.ini @@ -2,16 +2,17 @@ skip-if = buildapp == 'b2g' support-files = file_bug514732_window.xul frame_selection_underline-ref.xhtml frame_selection_underline.css frame_selection_underline.xhtml [test_backspace_delete.xul] +skip-if = true # Bug 1163311 [test_bug348681.html] [test_bug469613.xul] [test_bug469774.xul] [test_bug508115.xul] [test_bug514732-2.xul] [test_bug632379.xul] skip-if = buildapp == 'mulet' [test_selection_preventDefault.html]
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3064,16 +3064,44 @@ pref("intl.tsf.hack.easy_changjei.do_not // of selected clause of composition string. pref("intl.tsf.hack.google_ja_input.do_not_return_no_layout_error_at_first_char", true); // Whether use previous character rect for the result of // ITfContextView::GetTextExt() if the specified range is the caret of // composition string. pref("intl.tsf.hack.google_ja_input.do_not_return_no_layout_error_at_caret", true); #endif +// If composition_font is set, Gecko sets the font to IME. IME may use +// the fonts on their window like candidate window. If they are empty, +// Gecko uses the system default font which is set to the IM context. +// The font name must not start with '@'. When the writing mode is vertical, +// Gecko inserts '@' to the start of the font name automatically. +// FYI: Changing these prefs requires to restart. +pref("intl.imm.composition_font", ""); + +// Japanist 2003's candidate window is broken if the font is "@System" which +// is default composition font for vertical writing mode. +// You can specify font to use on candidate window of Japanist 2003. +// This value must not start with '@'. +// FYI: Changing this pref requires to restart. +pref("intl.imm.composition_font.japanist_2003", "MS PGothic"); + +// Even if IME claims that they support vertical writing mode but it may not +// support vertical writing mode for its candidate window. This pref allows +// to ignore the claim. +// FYI: Changing this pref requires to restart. +pref("intl.imm.vertical_writing.always_assume_not_supported", false); + +// We cannot retrieve active IME name with IMM32 API if a TIP of TSF is active. +// This pref can specify active IME name when Japanese TIP is active. +// For example: +// Google Japanese Input: "Google 日本語入力 IMM32 モジュール" +// ATOK 2011: "ATOK 2011" (similarly, e.g., ATOK 2013 is "ATOK 2013") +pref("intl.imm.japanese.assume_active_tip_name_as", ""); + // See bug 448927, on topmost panel, some IMEs are not usable on Windows. pref("ui.panel.default_level_parent", false); pref("mousewheel.system_scroll_override_on_root_content.enabled", true); // High resolution scrolling with supported mouse drivers on Vista or later. pref("mousewheel.enable_pixel_scrolling", true); @@ -4163,17 +4191,16 @@ pref("notification.feature.enabled", fal pref("dom.webnotifications.enabled", true); // Alert animation effect, name is disableSlidingEffect for backwards-compat. pref("alerts.disableSlidingEffect", false); // DOM full-screen API. pref("full-screen-api.enabled", false); pref("full-screen-api.allow-trusted-requests-only", true); -pref("full-screen-api.content-only", false); pref("full-screen-api.pointer-lock.enabled", true); // DOM idle observers API pref("dom.idle-observers-api.enabled", true); // Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput(). // Used to detect long running handlers of user-generated events. pref("dom.event.handling-user-input-time-limit", 1000);
--- a/python/eme/gen-eme-voucher.py +++ b/python/eme/gen-eme-voucher.py @@ -2,39 +2,46 @@ # # Copyright 2014 Adobe Systems Incorporated. All Rights Reserved. # # Adobe permits you to use, modify, and distribute this file in accordance # with the terms of the Mozilla Public License, v 2.0 accompanying it. If # a copy of the MPL was not distributed with this file, You can obtain one # at http://mozilla.org/MPL/2.0/. # -# Creates an Adobe Access signed voucher for any executable +# Creates an Adobe Access signed voucher for x32/x64 windows executables # Notes: This is currently python2.7 due to mozilla build system requirements +from __future__ import print_function + import argparse, bitstring, pprint, hashlib, os, subprocess, sys, tempfile from pyasn1.codec.der import encoder as der_encoder from pyasn1.type import univ, namedtype, namedval, constraint +# Defined in WinNT.h from the Windows SDK +IMAGE_SCN_MEM_EXECUTE = 0x20000000 +IMAGE_REL_BASED_HIGHLOW = 3 +IMAGE_REL_BASED_DIR64 = 10 + # CodeSectionDigest ::= SEQUENCE { # offset INTEGER -- section's file offset in the signed binary # digestAlgorithm OBJECT IDENTIFIER -- algorithm identifier for the hash value below. For now only supports SHA256. # digestValue OCTET STRING -- hash value of the TEXT segment. # } class CodeSectionDigest(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('offset', univ.Integer()), namedtype.NamedType('digestAlgorithm', univ.ObjectIdentifier()), namedtype.NamedType('digest', univ.OctetString())) # CodeSegmentDigest ::= SEQUENCE { -# offset INTEGER -- TEXT segment's file offset in the signed binary -# codeSectionDigests SET OF CodeSectionDigests +# offset INTEGER -- TEXT segment's file offset in the signed binary +# codeSectionDigests SET OF CodeSectionDigests # } class SetOfCodeSectionDigest(univ.SetOf): componentType = CodeSectionDigest() class CodeSegmentDigest(univ.Sequence): componentType = namedtype.NamedTypes( @@ -94,16 +101,17 @@ class ApplicationDigest(univ.Sequence): def meets_requirements(items, requirements): for r in requirements: for n, v in r.items(): if n not in items or items[n] != v: return False return True # return total number of bytes read from items_in excluding leaves +# TODO: research replacing this with the python built-in struct module def parse_items(stream, items_in, items_out): bits_read = 0 total_bits_read = 0 for item in items_in: name = item[0] t = item[1] bits = 1 if ":" not in t else int(t[t.index(":") + 1:]) @@ -310,27 +318,28 @@ class COFFFileHeader: for i in range(0, int((block_size - 8) / 2)): data = stream.read('uintle:16') typ = data >> 12 offset = data & 0xFFF if offset == 0 and i > 0: continue - assert(typ == 3) + assert(typ == IMAGE_REL_BASED_HIGHLOW or typ == IMAGE_REL_BASED_DIR64) cur_pos = stream.bitpos sh, value_bytepos = self.get_rva_section(page_rva + offset) stream.bytepos = value_bytepos - value = stream.read('uintle:32') + value = stream.read('uintle:32' if typ == IMAGE_REL_BASED_HIGHLOW else 'uintle:64') # remove BaseAddress value -= self.OptionalHeader.items['ImageBase'] - stream.overwrite(bitstring.BitArray(uint=value, length=4 * 8), pos=value_bytepos * 8) + bit_size = (4 if typ == IMAGE_REL_BASED_HIGHLOW else 8) * 8 + stream.overwrite(bitstring.BitArray(uint=value, length=bit_size), pos=value_bytepos * 8) stream.pos = cur_pos stream.bitpos = orig_pos def get_rva_section(self, rva): for sh in self.section_headers: if rva < sh.items['VirtualAddress'] or rva >= sh.items['VirtualAddress'] + sh.items['VirtualSize']: continue @@ -340,25 +349,66 @@ class COFFFileHeader: raise Exception('Could not match RVA to section') def create_temp_file(suffix=""): fd, path = tempfile.mkstemp(suffix=suffix) os.close(fd) return path - -# TIPS: -# How to convert PFX to PEM: openssl pkcs12 -in build/certificates/testPKI/IV.pfx -out build/certificates/testPKI/IV.cert.pem + + +class ExpandPath(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, os.path.abspath(os.path.expanduser(values))) + + +# this does a naming trick since windows doesn't allow multiple usernames for the same server +def get_password(service_name, user_name): + try: + import keyring + + # windows doesn't allow multiple usernames for the same server, argh + if sys.platform == "win32": + password = keyring.get_password(service_name + "-" + user_name, user_name) + else: + password = keyring.get_password(service_name, user_name) + + return password + except: + # This allows for manual testing where you do not wish to cache the password on the system + print("Missing keyring module...getting password manually") + + return None + + +def openssl_cmd(app_args, args, password_in, password_out): + password = get_password(app_args.password_service, app_args.password_user) if (password_in or password_out) else None + env = None + args = [app_args.openssl_path] + args + + if password is not None: + env = os.environ.copy() + env["COFF_PW"] = password + + if password_in: args += ["-passin", "env:COFF_PW"] + if password_out: args += ["-passout", "env:COFF_PW", "-password", "env:COFF_PW"] + + p = subprocess.Popen(args, env=env) + assert p.wait() == 0 + + def main(): parser = argparse.ArgumentParser(description='PE/COFF Signer') - parser.add_argument('-input', required=True, help="File to parse.") - parser.add_argument('-output', required=True, help="File to write to.") - parser.add_argument('-openssl_path',help="Path to OpenSSL to create signed voucher") - parser.add_argument('-signer_cert',help="Path to certificate to use to sign voucher. Must be PEM encoded.") + parser.add_argument('-input', action=ExpandPath, required=True, help="File to parse.") + parser.add_argument('-output', action=ExpandPath, required=True, help="File to write to.") + parser.add_argument('-openssl_path', action=ExpandPath, help="Path to OpenSSL to create signed voucher") + parser.add_argument('-signer_pfx', action=ExpandPath, help="Path to certificate to use to sign voucher. Must contain full certificate chain.") + parser.add_argument('-password_service', help="Name of Keyring/Wallet service/host") + parser.add_argument('-password_user', help="Name of Keyring/Wallet user name") parser.add_argument('-verbose', action='store_true', help="Verbose output.") app_args = parser.parse_args() # to simplify relocation handling we use a mutable BitStream so we can remove # the BaseAddress from each relocation stream = bitstring.BitStream(filename=app_args.input) # find the COFF header. @@ -380,17 +430,17 @@ def main(): arch_digest = ArchitectureDigest() if coff_header.items['Machine'] == 0x14c: arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_I386')) elif coff_header.items['Machine'] == 0x8664: arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_AMD64')) arch_digest.setComponentByName('cpuSubType', CPUSubType('IMAGE_UNUSED')) - text_section_headers = list(filter(lambda x: (x.items['Characteristics'] & 0x20000000) == 0x20000000, coff_header.section_headers)) + text_section_headers = list(filter(lambda x: (x.items['Characteristics'] & IMAGE_SCN_MEM_EXECUTE) == IMAGE_SCN_MEM_EXECUTE, coff_header.section_headers)) code_segment_digests = SetOfCodeSegmentDigest() code_segment_idx = 0 for code_sect_header in text_section_headers: stream.bytepos = code_sect_header.offset code_sect_bytes = stream.read('bytes:' + str(code_sect_header.items['VirtualSize'])) digester = hashlib.sha256() @@ -427,28 +477,43 @@ def main(): binaryDigest = der_encoder.encode(appDigest) with open(app_args.output, 'wb') as f: f.write(binaryDigest) # sign with openssl if specified if app_args.openssl_path is not None: - assert app_args.signer_cert is not None - + assert app_args.signer_pfx is not None + out_base, out_ext = os.path.splitext(app_args.output) signed_path = out_base + ".signed" + out_ext - + # http://stackoverflow.com/questions/12507277/how-to-fix-unable-to-write-random-state-in-openssl - temp_file = None + temp_files = [] if sys.platform == "win32" and "RANDFILE" not in os.environ: temp_file = create_temp_file() + temp_files += [temp_file] os.environ["RANDFILE"] = temp_file - + try: - subprocess.check_call([app_args.openssl_path, "cms", "-sign", "-nodetach", "-md", "sha256", "-binary", "-in", app_args.output, "-outform", "der", "-out", signed_path, "-signer", app_args.signer_cert], ) + # create PEM from PFX + pfx_pem_path = create_temp_file(".pem") + temp_files += [pfx_pem_path] + print("Extracting PEM from PFX to:" + pfx_pem_path) + openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-out", pfx_pem_path], True, True) + + # extract CA certs + pfx_cert_path = create_temp_file(".cert") + temp_files += [pfx_cert_path] + print("Extracting cert from PFX to:" + pfx_cert_path) + openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-cacerts", "-nokeys", "-out", pfx_cert_path], True, False) + + # we embed the public keychain for client validation + openssl_cmd(app_args, ["cms", "-sign", "-nodetach", "-md", "sha256", "-binary", "-in", app_args.output, "-outform", "der", "-out", signed_path, "-signer", pfx_pem_path, "-certfile", pfx_cert_path], True, False) finally: - if temp_file is not None: - del os.environ["RANDFILE"] - os.unlink(temp_file) + for t in temp_files: + if "RANDFILE" in os.environ and t == os.environ["RANDFILE"]: + del os.environ["RANDFILE"] + os.unlink(t) if __name__ == '__main__': main()
--- a/security/apps/AppTrustDomain.cpp +++ b/security/apps/AppTrustDomain.cpp @@ -229,17 +229,17 @@ AppTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) { return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); } Result -AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time, +AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) { // We don't currently do revocation checking. If we need to distrust an Apps // certificate, we will use the active distrust mechanism. return Success; }
--- a/security/apps/AppTrustDomain.h +++ b/security/apps/AppTrustDomain.h @@ -29,16 +29,17 @@ public: /*out*/ mozilla::pkix::TrustLevel& trustLevel) override; virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, mozilla::pkix::Time time) override; virtual Result CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, + mozilla::pkix::Duration validityDuration, /*optional*/ const mozilla::pkix::Input* stapledOCSPresponse, /*optional*/ const mozilla::pkix::Input* aiaExtension) override; virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time) override; virtual Result CheckSignatureDigestAlgorithm( mozilla::pkix::DigestAlgorithm digestAlg) override; virtual Result CheckRSAPublicKeyModulusSizeInBits( mozilla::pkix::EndEntityOrCA endEntityOrCA,
--- a/security/certverifier/CertVerifier.cpp +++ b/security/certverifier/CertVerifier.cpp @@ -27,20 +27,22 @@ PRLogModuleInfo* gCertVerifierLog = null namespace mozilla { namespace psm { const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1; const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2; CertVerifier::CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc, OcspGetConfig ogc, + uint32_t certShortLifetimeInDays, PinningMode pinningMode) : mOCSPDownloadEnabled(odc == ocspOn) , mOCSPStrict(osc == ocspStrict) , mOCSPGETEnabled(ogc == ocspGetEnabled) + , mCertShortLifetimeInDays(certShortLifetimeInDays) , mPinningMode(pinningMode) { } CertVerifier::~CertVerifier() { } @@ -188,17 +190,19 @@ CertVerifier::VerifyCert(CERTCertificate stapledOCSPResponse = &stapledOCSPResponseInput; } switch (usage) { case certificateUsageSSLClient: { // XXX: We don't really have a trust bit for SSL client authentication so // just use trustEmail as it is the closest alternative. NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, pinningDisabled, + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, nullptr, builtChain); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::digitalSignature, KeyPurposeId::id_kp_clientAuth, CertPolicyId::anyPolicy, stapledOCSPResponse); break; } @@ -214,18 +218,19 @@ CertVerifier::VerifyCert(CERTCertificate SECOidTag evPolicyOidTag; SECStatus srv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag); if (srv == SECSuccess) { NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV : NSSCertDBTrustDomain::FetchOCSPForEV, - mOCSPCache, pinArg, ocspGETConfig, mPinningMode, - MIN_RSA_BITS, hostname, builtChain); + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS, + hostname, builtChain); rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time, KeyUsage::digitalSignature,// (EC)DHE KeyUsage::keyEncipherment, // RSA KeyUsage::keyAgreement, // (EC)DH KeyPurposeId::id_kp_serverAuth, evPolicy, stapledOCSPResponse, ocspStaplingStatus); if (rv == Success) { @@ -239,17 +244,18 @@ CertVerifier::VerifyCert(CERTCertificate if (flags & FLAG_MUST_BE_EV) { rv = Result::ERROR_POLICY_VALIDATION_FAILED; break; } // Now try non-EV. NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, mPinningMode, + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS, hostname, builtChain); rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time, KeyUsage::digitalSignature, // (EC)DHE KeyUsage::keyEncipherment, // RSA KeyUsage::keyAgreement, // (EC)DH KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy, stapledOCSPResponse, @@ -258,19 +264,20 @@ CertVerifier::VerifyCert(CERTCertificate if (keySizeStatus) { *keySizeStatus = KeySizeStatus::LargeMinimumSucceeded; } break; } // If that failed, try again with a smaller minimum key size. NSSCertDBTrustDomain trustDomainWeak(trustSSL, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, mPinningMode, - MIN_RSA_BITS_WEAK, hostname, - builtChain); + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + mPinningMode, MIN_RSA_BITS_WEAK, + hostname, builtChain); rv = BuildCertChainForOneKeyUsage(trustDomainWeak, certDER, time, KeyUsage::digitalSignature, // (EC)DHE KeyUsage::keyEncipherment, // RSA KeyUsage::keyAgreement, // (EC)DH KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy, stapledOCSPResponse, ocspStaplingStatus); @@ -282,29 +289,33 @@ CertVerifier::VerifyCert(CERTCertificate } } break; } case certificateUsageSSLCA: { NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign, KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy, stapledOCSPResponse); break; } case certificateUsageEmailSigner: { NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::digitalSignature, KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, @@ -315,18 +326,20 @@ CertVerifier::VerifyCert(CERTCertificate break; } case certificateUsageEmailRecipient: { // TODO: The higher level S/MIME processing should pass in which key // usage it is trying to verify for, and base its algorithm choices // based on the result of the verification(s). NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::keyEncipherment, // RSA KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, @@ -335,18 +348,19 @@ CertVerifier::VerifyCert(CERTCertificate CertPolicyId::anyPolicy, stapledOCSPResponse); } break; } case certificateUsageObjectSigner: { NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching, mOCSPCache, pinArg, ocspGETConfig, - pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::digitalSignature, KeyPurposeId::id_kp_codeSigning, CertPolicyId::anyPolicy, stapledOCSPResponse); break; } @@ -364,32 +378,36 @@ CertVerifier::VerifyCert(CERTCertificate eku = KeyPurposeId::anyExtendedKeyUsage; } else { endEntityOrCA = EndEntityOrCA::MustBeEndEntity; keyUsage = KeyUsage::digitalSignature; eku = KeyPurposeId::id_kp_OCSPSigning; } NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache, pinArg, - ocspGETConfig, pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + ocspGETConfig, mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA, keyUsage, eku, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_UNKNOWN_ISSUER) { NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache, - pinArg, ocspGETConfig, pinningDisabled, - MIN_RSA_BITS_WEAK, nullptr, builtChain); + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + nullptr, builtChain); rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA, keyUsage, eku, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_UNKNOWN_ISSUER) { NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning, ocspFetching, mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, pinningDisabled, MIN_RSA_BITS_WEAK, nullptr, builtChain); rv = BuildCertChain(objectSigningTrust, certDER, time, endEntityOrCA, keyUsage, eku, CertPolicyId::anyPolicy, stapledOCSPResponse); } }
--- a/security/certverifier/CertVerifier.h +++ b/security/certverifier/CertVerifier.h @@ -77,24 +77,26 @@ public: enum OcspDownloadConfig { ocspOff = 0, ocspOn }; enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict }; enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 }; bool IsOCSPDownloadEnabled() const { return mOCSPDownloadEnabled; } CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc, - OcspGetConfig ogc, PinningMode pinningMode); + OcspGetConfig ogc, uint32_t certShortLifetimeInDays, + PinningMode pinningMode); ~CertVerifier(); void ClearOCSPCache() { mOCSPCache.Clear(); } const bool mOCSPDownloadEnabled; const bool mOCSPStrict; const bool mOCSPGETEnabled; + const uint32_t mCertShortLifetimeInDays; const PinningMode mPinningMode; private: OCSPCache mOCSPCache; }; void InitCertVerifierLog(); SECStatus IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
--- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -40,25 +40,27 @@ namespace mozilla { namespace psm { const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module"; NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching, OCSPCache& ocspCache, /*optional but shouldn't be*/ void* pinArg, CertVerifier::OcspGetConfig ocspGETConfig, + uint32_t certShortLifetimeInDays, CertVerifier::PinningMode pinningMode, unsigned int minRSABits, /*optional*/ const char* hostname, /*optional*/ ScopedCERTCertList* builtChain) : mCertDBTrustType(certDBTrustType) , mOCSPFetching(ocspFetching) , mOCSPCache(ocspCache) , mPinArg(pinArg) , mOCSPGetConfig(ocspGETConfig) + , mCertShortLifetimeInDays(certShortLifetimeInDays) , mPinningMode(pinningMode) , mMinRSABits(minRSABits) , mHostname(hostname) , mBuiltChain(builtChain) , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED) { } @@ -324,16 +326,17 @@ GetOCSPAuthorityInfoAccessLocation(PLAre } return Success; } Result NSSCertDBTrustDomain::CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID& certID, Time time, + Duration validityDuration, /*optional*/ const Input* stapledOCSPResponse, /*optional*/ const Input* aiaExtension) { // Actively distrusted certificates will have already been blocked by // GetCertTrust. // TODO: need to verify that IsRevoked isn't called for trust anchors AND // that that fact is documented in mozillapkix. @@ -450,17 +453,20 @@ NSSCertDBTrustDomain::CheckRevocation(En return Result::FATAL_ERROR_LIBRARY_FAILURE; } // TODO: We still need to handle the fallback for expired responses. But, // if/when we disable OCSP fetching by default, it would be ambiguous whether // security.OCSP.enable==0 means "I want the default" or "I really never want // you to ever fetch OCSP." + Duration shortLifetime(mCertShortLifetimeInDays * Time::ONE_DAY_IN_SECONDS); + if ((mOCSPFetching == NeverFetchOCSP) || + (validityDuration < shortLifetime) || (endEntityOrCA == EndEntityOrCA::MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail || mOCSPFetching == FetchOCSPForDVSoftFail || blocklistIsFresh))) { // We're not going to be doing any fetching, so if there was a cached // "unknown" response, say so. if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { return Result::ERROR_OCSP_UNKNOWN_CERT;
--- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -48,16 +48,17 @@ public: FetchOCSPForDVHardFail = 2, FetchOCSPForEV = 3, LocalOnlyOCSPForEV = 4, }; NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching, OCSPCache& ocspCache, void* pinArg, CertVerifier::OcspGetConfig ocspGETConfig, + uint32_t certShortLifetimeInDays, CertVerifier::PinningMode pinningMode, unsigned int minRSABits, /*optional*/ const char* hostname = nullptr, /*optional out*/ ScopedCERTCertList* builtChain = nullptr); virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, mozilla::pkix::Time time) override; @@ -91,16 +92,17 @@ public: mozilla::pkix::DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) override; virtual Result CheckRevocation( mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, + mozilla::pkix::Duration validityDuration, /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse, /*optional*/ const mozilla::pkix::Input* aiaExtension) override; virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time) override; CertVerifier::OCSPStaplingStatus GetOCSPStaplingStatus() const @@ -122,16 +124,17 @@ private: uint16_t maxLifetimeInDays, mozilla::pkix::Input encodedResponse, EncodedResponseSource responseSource, /*out*/ bool& expired); const SECTrustType mCertDBTrustType; const OCSPFetching mOCSPFetching; OCSPCache& mOCSPCache; // non-owning! void* mPinArg; // non-owning! const CertVerifier::OcspGetConfig mOCSPGetConfig; + const uint32_t mCertShortLifetimeInDays; CertVerifier::PinningMode mPinningMode; const unsigned int mMinRSABits; const char* mHostname; // non-owning - only used for pinning checks ScopedCERTCertList* mBuiltChain; // non-owning nsCOMPtr<nsICertBlocklist> mCertBlocklist; CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus; };
--- a/security/manager/ssl/src/SharedCertVerifier.h +++ b/security/manager/ssl/src/SharedCertVerifier.h @@ -15,17 +15,19 @@ class SharedCertVerifier : public mozill { protected: ~SharedCertVerifier(); public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier) SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc, - OcspGetConfig ogc, PinningMode pinningMode) - : mozilla::psm::CertVerifier(odc, osc, ogc, pinningMode) + OcspGetConfig ogc, uint32_t certShortLifetimeInDays, + PinningMode pinningMode) + : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays, + pinningMode) { } }; } } // namespace mozilla::psm #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -176,40 +176,49 @@ bool EnsureNSSInitialized(EnsureNSSOpera default: NS_ASSERTION(false, "Bad operator to EnsureNSSInitialized"); return false; } } static void -GetOCSPBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc, - /*out*/ CertVerifier::OcspStrictConfig* osc, - /*out*/ CertVerifier::OcspGetConfig* ogc, - const MutexAutoLock& /*proofOfLock*/) +GetRevocationBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc, + /*out*/ CertVerifier::OcspStrictConfig* osc, + /*out*/ CertVerifier::OcspGetConfig* ogc, + /*out*/ uint32_t* certShortLifetimeInDays, + const MutexAutoLock& /*proofOfLock*/) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(odc); MOZ_ASSERT(osc); MOZ_ASSERT(ogc); + MOZ_ASSERT(certShortLifetimeInDays); // 0 = disabled, otherwise enabled *odc = Preferences::GetInt("security.OCSP.enabled", 1) ? CertVerifier::ocspOn : CertVerifier::ocspOff; *osc = Preferences::GetBool("security.OCSP.require", false) ? CertVerifier::ocspStrict : CertVerifier::ocspRelaxed; // XXX: Always use POST for OCSP; see bug 871954 for undoing this. *ogc = Preferences::GetBool("security.OCSP.GET.enabled", false) ? CertVerifier::ocspGetEnabled : CertVerifier::ocspGetDisabled; + // If we pass in just 0 as the second argument to Preferences::GetUint, there + // are two function signatures that match (given that 0 can be intepreted as + // a null pointer). Thus the compiler will complain without the cast. + *certShortLifetimeInDays = + Preferences::GetUint("security.pki.cert_short_lifetime_in_days", + static_cast<uint32_t>(0)); + SSL_ClearSessionCache(); } nsNSSComponent::nsNSSComponent() :mutex("nsNSSComponent.mutex"), mNSSInitialized(false), #ifndef MOZ_NO_SMART_CARDS mThreadList(nullptr), @@ -870,19 +879,23 @@ void nsNSSComponent::setValidationOption CertVerifier::pinningDisabled)); if (pinningMode > CertVerifier::pinningEnforceTestMode) { pinningMode = CertVerifier::pinningDisabled; } CertVerifier::OcspDownloadConfig odc; CertVerifier::OcspStrictConfig osc; CertVerifier::OcspGetConfig ogc; + uint32_t certShortLifetimeInDays; - GetOCSPBehaviorFromPrefs(&odc, &osc, &ogc, lock); - mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc, pinningMode); + GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays, + lock); + mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc, + certShortLifetimeInDays, + pinningMode); } // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and // TLS 1.2 (max) when the prefs aren't set or set to invalid values. nsresult nsNSSComponent::setEnabledTLSVersions() { // keep these values in sync with security-prefs.js @@ -1339,16 +1352,17 @@ nsNSSComponent::Observe(nsISupports* aSu SSL_OptionSetDefault(SSL_ENABLE_ALPN, Preferences::GetBool("security.ssl.enable_alpn", ALPN_ENABLED_DEFAULT)); } else if (prefName.Equals("security.ssl.disable_session_identifiers")) { ConfigureTLSSessionIdentifiers(); } else if (prefName.EqualsLiteral("security.OCSP.enabled") || prefName.EqualsLiteral("security.OCSP.require") || prefName.EqualsLiteral("security.OCSP.GET.enabled") || + prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") || prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") || prefName.EqualsLiteral("security.cert_pinning.enforcement_level")) { MutexAutoLock lock(mutex); setValidationOptions(false, lock); } else { clearSessionCache = false; } if (clearSessionCache)
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js +++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js @@ -42,16 +42,49 @@ function run_test() { add_tests(); add_test(function() { ocspResponder.stop(run_next_test); }); run_next_test(); } function add_tests() { + // Test that verifying a certificate with a "short lifetime" doesn't result + // in OCSP fetching. Due to longevity requirements in our testing + // infrastructure, the certificate we encounter is valid for a very long + // time, so we have to define a "short lifetime" as something very long. + add_test(function() { + Services.prefs.setIntPref("security.pki.cert_short_lifetime_in_days", + 12000); + run_next_test(); + }); + add_connection_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess, + clearSessionCache); + add_test(function() { + Assert.equal(0, gFetchCount, + "expected zero OCSP requests for a short-lived certificate"); + Services.prefs.setIntPref("security.pki.cert_short_lifetime_in_days", 100); + run_next_test(); + }); + // If a "short lifetime" is something more reasonable, ensure that we do OCSP + // fetching for this long-lived certificate. + add_connection_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess, + clearSessionCache); + add_test(function() { + Assert.equal(1, gFetchCount, + "expected one OCSP request for a long-lived certificate"); + Services.prefs.clearUserPref("security.pki.cert_short_lifetime_in_days"); + run_next_test(); + }); + + //--------------------------------------------------------------------------- + + // Reset state + add_test(function() { clearOCSPCache(); gFetchCount = 0; run_next_test(); }); + // This test assumes that OCSPStaplingServer uses the same cert for // ocsp-stapling-unknown.example.com and ocsp-stapling-none.example.com. // Get an Unknown response for the *.exmaple.com cert and put it in the // OCSP cache. add_connection_test("ocsp-stapling-unknown.example.com", SEC_ERROR_OCSP_UNKNOWN_CERT, clearSessionCache);
--- a/security/pkix/include/pkix/Time.h +++ b/security/pkix/include/pkix/Time.h @@ -102,25 +102,50 @@ private: // // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)! // return Time(t); // } explicit Time(uint64_t elapsedSecondsAD) : elapsedSecondsAD(elapsedSecondsAD) { } friend Time TimeFromElapsedSecondsAD(uint64_t); + friend class Duration; uint64_t elapsedSecondsAD; }; inline Time TimeFromElapsedSecondsAD(uint64_t elapsedSecondsAD) { return Time(elapsedSecondsAD); } Time Now(); // Note the epoch is the unix epoch (ie 00:00:00 UTC, 1 January 1970) Time TimeFromEpochInSeconds(uint64_t secondsSinceEpoch); +class Duration final +{ +public: + Duration(Time timeA, Time timeB) + : durationInSeconds(timeA < timeB + ? timeB.elapsedSecondsAD - timeA.elapsedSecondsAD + : timeA.elapsedSecondsAD - timeB.elapsedSecondsAD) + { + } + + explicit Duration(uint64_t durationInSeconds) + : durationInSeconds(durationInSeconds) + { + } + + bool operator<(const Duration& other) const + { + return durationInSeconds < other.durationInSeconds; + } + +private: + uint64_t durationInSeconds; +}; + } } // namespace mozilla::pkix #endif // mozilla_pkix_Time_h
--- a/security/pkix/include/pkix/pkixtypes.h +++ b/security/pkix/include/pkix/pkixtypes.h @@ -263,16 +263,17 @@ public: // certificate chain passed to IsChainValid; especially, it would be very // wrong to assume that the certificate chain is valid. // // certChain.GetDER(0) is the trust anchor. virtual Result IsChainValid(const DERArray& certChain, Time time) = 0; virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID& certID, Time time, + Duration validityDuration, /*optional*/ const Input* stapledOCSPresponse, /*optional*/ const Input* aiaExtension) = 0; // Check that the given digest algorithm is acceptable for use in signatures. // // Return Success if the algorithm is acceptable, // Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not // acceptable, or another error code if another error occurred.
--- a/security/pkix/lib/pkixbuild.cpp +++ b/security/pkix/lib/pkixbuild.cpp @@ -219,18 +219,27 @@ PathBuildingStep::Check(Input potentialI // We avoid doing revocation checking for expired certificates because OCSP // responders are allowed to forget about expired certificates, and many OCSP // responders return an error when asked for the status of an expired // certificate. if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) { CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(), subject.GetSerialNumber()); + Time notBefore(Time::uninitialized); + Time notAfter(Time::uninitialized); + // This should never fail. If we're here, we've already checked that the + // given time is in the certificate's validity period. + rv = CheckValidity(subject.GetValidity(), time, ¬Before, ¬After); + if (rv != Success) { + return rv; + } + Duration validityDuration(notAfter, notBefore); rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time, - stapledOCSPResponse, + validityDuration, stapledOCSPResponse, subject.GetAuthorityInfoAccess()); if (rv != Success) { return RecordResult(rv, keepGoing); } } return RecordResult(Success, keepGoing); }
--- a/security/pkix/lib/pkixcheck.cpp +++ b/security/pkix/lib/pkixcheck.cpp @@ -120,17 +120,19 @@ CheckSignatureAlgorithm(TrustDomain& tru } return Success; } // 4.1.2.5 Validity Result -CheckValidity(Input encodedValidity, Time time) +CheckValidity(Input encodedValidity, Time time, + /*optional out*/ Time* notBeforeOut, + /*optional out*/ Time* notAfterOut) { Reader validity(encodedValidity); Time notBefore(Time::uninitialized); if (der::TimeChoice(validity, notBefore) != Success) { return Result::ERROR_INVALID_DER_TIME; } Time notAfter(Time::uninitialized); @@ -149,16 +151,22 @@ CheckValidity(Input encodedValidity, Tim if (time < notBefore) { return Result::ERROR_NOT_YET_VALID_CERTIFICATE; } if (time > notAfter) { return Result::ERROR_EXPIRED_CERTIFICATE; } + if (notBeforeOut) { + *notBeforeOut = notBefore; + } + if (notAfterOut) { + *notAfterOut = notAfter; + } return Success; } // 4.1.2.7 Subject Public Key Info Result CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA)
--- a/security/pkix/lib/pkixcheck.h +++ b/security/pkix/lib/pkixcheck.h @@ -40,11 +40,15 @@ Result CheckIssuerIndependentProperties( const CertPolicyId& requiredPolicy, unsigned int subCACount, /*out*/ TrustLevel& trustLevel); Result CheckNameConstraints(Input encodedNameConstraints, const BackCert& firstChild, KeyPurposeId requiredEKUIfPresent); +Result CheckValidity(Input encodedValidity, Time time, + /*optional out*/ Time* notBeforeOut = nullptr, + /*optional out*/ Time* notAfterOut = nullptr); + } } // namespace mozilla::pkix #endif // mozilla_pkix_pkixcheck_h
--- a/security/pkix/test/gtest/pkixbuild_tests.cpp +++ b/security/pkix/test/gtest/pkixbuild_tests.cpp @@ -134,17 +134,17 @@ private: rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/, keepGoing); if (rv != Success) { return rv; } return Success; } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) override { return Success; } Result IsChainValid(const DERArray&, Time) override { @@ -398,17 +398,17 @@ public: bool keepGoing; EXPECT_EQ(Success, checker.Check(issuerInput, nullptr /*additionalNameConstraints*/, keepGoing)); EXPECT_EQ(expectedKeepGoing, keepGoing); return Success; } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) override { return Success; } Result IsChainValid(const DERArray&, Time) override {
--- a/security/pkix/test/gtest/pkixcert_extension_tests.cpp +++ b/security/pkix/test/gtest/pkixcert_extension_tests.cpp @@ -63,17 +63,17 @@ class TrustEverythingTrustDomain final : private: Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input, /*out*/ TrustLevel& trustLevel) override { trustLevel = TrustLevel::TrustAnchor; return Success; } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) override { return Success; } Result IsChainValid(const DERArray&, Time) override {
--- a/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp +++ b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp @@ -86,18 +86,18 @@ private: Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length()); if (rv != Success) { return rv; } bool keepGoing; return checker.Check(issuerCert, nullptr, keepGoing); } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, const Input*, - const Input*) override + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + const Input*, const Input*) override { return Success; } Result IsChainValid(const DERArray&, Time) override { return Success; }
--- a/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp +++ b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp @@ -287,17 +287,17 @@ public: bool keepGoing; EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing)); EXPECT_FALSE(keepGoing); return Success; } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) override { return Success; } Result IsChainValid(const DERArray&, Time) override {
--- a/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp +++ b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp @@ -17,27 +17,22 @@ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ +#include "pkixcheck.h" #include "pkixgtest.h" using namespace mozilla::pkix; using namespace mozilla::pkix::test; -namespace mozilla { namespace pkix { - -Result CheckValidity(const Input encodedValidity, Time time); - -} } // namespace mozilla::pkix - static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56)); #define OLDER_GENERALIZEDTIME \ 0x18, 15, /* tag, length */ \ '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ #define OLDER_UTCTIME \
--- a/security/pkix/test/gtest/pkixgtest.h +++ b/security/pkix/test/gtest/pkixgtest.h @@ -97,17 +97,17 @@ public: Result FindIssuer(Input, IssuerChecker&, Time) override { ADD_FAILURE(); return NotReached("FindIssuer should not be called", Result::FATAL_ERROR_LIBRARY_FAILURE); } - Result CheckRevocation(EndEntityOrCA, const CertID&, Time, + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*) override { ADD_FAILURE(); return NotReached("CheckRevocation should not be called", Result::FATAL_ERROR_LIBRARY_FAILURE); }
--- a/security/sandbox/linux/SandboxChroot.cpp +++ b/security/sandbox/linux/SandboxChroot.cpp @@ -73,18 +73,29 @@ AlwaysClose(int fd) static int OpenDeletedDirectory() { // We don't need this directory to persist between invocations of // the program (nor need it to be cleaned up if something goes wrong // here, because mkdtemp will choose a fresh name), so /tmp as // specified by FHS is adequate. - char path[] = "/tmp/mozsandbox.XXXXXX"; - if (!mkdtemp(path)) { + // + // However, this needs a filesystem where a deleted directory can + // still be used, and /tmp is sometimes not that; e.g., aufs(5), + // often used for containers, will cause the chroot() to fail with + // ESTALE (bug 1162965). So this uses /dev/shm if possible instead. + char tmpPath[] = "/tmp/mozsandbox.XXXXXX"; + char shmPath[] = "/dev/shm/mozsandbox.XXXXXX"; + char* path; + if (mkdtemp(shmPath)) { + path = shmPath; + } else if (mkdtemp(tmpPath)) { + path = tmpPath; + } else { SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno)); return -1; } int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY)); if (fd < 0) { SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno)); // Try to clean up. Shouldn't fail, but livable if it does. DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5556,16 +5556,22 @@ }, "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETHREAD_MS": { "expires_in_version": "never", "kind": "exponential", "high": "10000", "n_buckets": "1000", "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip." }, + "MEDIA_WMF_DECODE_ERROR": { + "expires_in_version": "50", + "kind": "enumerated", + "n_values": 256, + "description": "WMF media decoder error or success (0) codes." + }, "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG": { "expires_in_version": "50", "kind": "enumerated", "n_values": 128, "description": "The H.264 constraint set flag as extracted from the codecs parameter passed to HTMLMediaElement.canPlayType, with the addition of 0 for unknown values." }, "VIDEO_CANPLAYTYPE_H264_LEVEL": { "expires_in_version": "50", @@ -7535,16 +7541,30 @@ "kind": "count" }, "VIDEO_MSE_UNLOAD_STATE": { "expires_in_version": "45", "kind": "enumerated", "n_values": 5, "description": "MSE video state when unloading. ended = 0, paused = 1, stalled = 2, seeking = 3, other = 4" }, + "VIDEO_ADOBE_GMP_DISAPPEARED": { + "alert_emails": ["cpearce@mozilla.com"], + "expires_in_version": "42", + "kind": "flag", + "description": "Whether or not the Adobe EME GMP was expected to be resident on disk but mysteriously isn't.", + "releaseChannelCollection": "opt-out" + }, + "VIDEO_OPENH264_GMP_DISAPPEARED": { + "alert_emails": ["cpearce@mozilla.com", "rjesup@mozilla.com"], + "expires_in_version": "42", + "kind": "flag", + "description": "Whether or not the OpenH264 GMP was expected to be resident on disk but mysteriously isn't.", + "releaseChannelCollection": "opt-out" + }, "FX_SANITIZE_TOTAL": { "alert_emails": ["firefox-dev@mozilla.org", "gavin@mozilla.com"], "expires_in_version": "50", "kind": "exponential", "high": "30000", "n_buckets": 20, "extended_statistics_ok": true, "description": "Sanitize: Total time it takes to sanitize (ms)"
--- a/toolkit/crashreporter/test/dumputils.cpp +++ b/toolkit/crashreporter/test/dumputils.cpp @@ -68,18 +68,20 @@ DumpCheckMemory(const char* dump_file) if (!memory_list) { return false; } void *addr; FILE *fp = fopen("crash-addr", "r"); if (!fp) return false; - if (fscanf(fp, "%p", &addr) != 1) + if (fscanf(fp, "%p", &addr) != 1) { + fclose(fp); return false; + } fclose(fp); remove("crash-addr"); MinidumpMemoryRegion* region = memory_list->GetMemoryRegionForAddress(uint64_t(addr)); if(!region) return false;
--- a/toolkit/modules/GMPUtils.jsm +++ b/toolkit/modules/GMPUtils.jsm @@ -84,18 +84,18 @@ this.GMPPrefs = { KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck", KEY_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks", KEY_APP_DISTRIBUTION: "distribution.id", KEY_APP_DISTRIBUTION_VERSION: "distribution.version", KEY_BUILDID: "media.gmp-manager.buildID", KEY_CERTS_BRANCH: "media.gmp-manager.certs.", KEY_PROVIDER_ENABLED: "media.gmp-provider.enabled", KEY_LOG_BASE: "media.gmp.log.", - KEY_LOGGING_LEVEL: this.KEY_LOG_BASE + "level", - KEY_LOGGING_DUMP: this.KEY_LOG_BASE + "dump", + KEY_LOGGING_LEVEL: "media.gmp.log.level", + KEY_LOGGING_DUMP: "media.gmp.log.dump", /** * Obtains the specified preference in relation to the specified plugin. * @param aKey The preference key value to use. * @param aDefaultValue The default value if no preference exists. * @param aPlugin The plugin to scope the preference to. * @return The obtained preference value, or the defaultValue if none exists. */
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm +++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm @@ -43,38 +43,42 @@ const GMP_PLUGINS = [ id: OPEN_H264_ID, name: "openH264_name", description: "openH264_description2", // The following licenseURL is part of an awful hack to include the OpenH264 // license without having bug 624602 fixed yet, and intentionally ignores // localisation. licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt", homepageURL: "http://www.openh264.org/", - optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul" + optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul", + missingKey: "VIDEO_OPENH264_GMP_DISAPPEARED", }, { id: EME_ADOBE_ID, name: "eme-adobe_name", description: "eme-adobe_description", // The following learnMoreURL is another hack to be able to support a SUMO page for this // feature. get learnMoreURL() { return Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content"; }, licenseURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM_EULA/index.html", homepageURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM", optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul", - isEME: true + isEME: true, + missingKey: "VIDEO_ADOBE_GMP_DISAPPEARED", }]; XPCOMUtils.defineLazyGetter(this, "pluginsBundle", () => Services.strings.createBundle("chrome://global/locale/plugins.properties")); XPCOMUtils.defineLazyGetter(this, "gmpService", () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService)); +XPCOMUtils.defineLazyGetter(this, "telemetryService", () => Services.telemetry); + let messageManager = Cc["@mozilla.org/globalmessagemanager;1"] .getService(Ci.nsIMessageListenerManager); let gLogger; let gLogAppenderDump = null; function configureLogging() { if (!gLogger) { @@ -135,16 +139,20 @@ GMPWrapper.prototype = { this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir, this._plugin.id, GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id)); } return this._gmpPath; }, + get missingKey() { + return this._plugin.missingKey; + }, + get id() { return this._plugin.id; }, get type() { return "plugin"; }, get isGMPlugin() { return true; }, get name() { return this._plugin.name; }, get creator() { return null; }, get homepageURL() { return this._plugin.homepageURL; }, get description() { return this._plugin.description; }, @@ -442,16 +450,53 @@ GMPWrapper.prototype = { this.onPrefVersionChanged, this); if (this._plugin.isEME) { Preferences.ignore(GMPPrefs.KEY_EME_ENABLED, this.onPrefEMEGlobalEnabledChanged, this); messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this); } return this._updateTask; }, + + _arePluginFilesOnDisk: function () { + let fileExists = function(aGmpPath, aFileName) { + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + let path = OS.Path.join(aGmpPath, aFileName); + f.initWithPath(path); + return f.exists(); + }; + + // Determine the name of the GMP dynamic library; it differs on every + // platform. Note: we can't use Services.appInfo.OS here, as that's + // "XPCShell" in our tests. + let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc); + let isOSX = ("nsILocalFileMac" in Ci); + let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc); + + let libName = ""; + let id = this._plugin.id; + if (isWindows) { + libName = id.substring(4) + ".dll"; + } else if (isOSX) { + libName = "lib" + id.substring(4) + ".dylib"; + } else if (isLinux) { + libName = id.substring(4) + ".so"; + } else { + this._info.error("_arePluginFilesOnDisk - unsupported platform."); + return false; + } + + return fileExists(this.gmpPath, libName) && + fileExists(this.gmpPath, id.substring(4) + ".info"); + }, + + validate: function() { + return !this.isInstalled || + this._arePluginFilesOnDisk(); + }, }; let GMPProvider = { get name() { return "GMPProvider"; }, _plugins: null, startup: function() { @@ -467,16 +512,23 @@ let GMPProvider = { for (let [id, plugin] of this._plugins) { let wrapper = plugin.wrapper; let gmpPath = wrapper.gmpPath; let isEnabled = wrapper.isActive; this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" + gmpPath); if (gmpPath && isEnabled) { + if (!wrapper.validate()) { + this._log.info("startup - gmp " + plugin.id + + " missing lib and/or info files, uninstalling"); + telemetryService.getHistogramById(wrapper.missingKey).add(true); + wrapper.uninstallPlugin(); + continue; + } this._log.info("startup - adding gmp directory " + gmpPath); try { gmpService.addPluginDirectory(gmpPath); } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { this._log.warn("startup - adding gmp directory failed with " + e.name + " - sandboxing not available?", e); } } @@ -582,16 +634,17 @@ let GMPProvider = { let plugin = { id: aPlugin.id, name: pluginsBundle.GetStringFromName(aPlugin.name), description: pluginsBundle.GetStringFromName(aPlugin.description), homepageURL: aPlugin.homepageURL, optionsURL: aPlugin.optionsURL, wrapper: null, isEME: aPlugin.isEME, + missingKey: aPlugin.missingKey, }; plugin.fullDescription = this.generateFullDescription(aPlugin); plugin.wrapper = new GMPWrapper(plugin); this._plugins.set(plugin.id, plugin); } }, ensureProperCDMInstallState: function() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js @@ -4,26 +4,30 @@ "use strict"; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; let GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm"); XPCOMUtils.defineLazyGetter(this, "pluginsBundle", () => Services.strings.createBundle("chrome://global/locale/plugins.properties")); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + let gMockAddons = new Map(); let gMockEmeAddons = new Map(); for (let plugin of GMPScope.GMP_PLUGINS) { let mockAddon = Object.freeze({ id: plugin.id, isValid: true, isInstalled: false, nameId: plugin.name, descriptionId: plugin.description, + missingKey: plugin.missingKey, }); gMockAddons.set(mockAddon.id, mockAddon); if (mockAddon.id.indexOf("gmp-eme-") == 0) { gMockEmeAddons.set(mockAddon.id, mockAddon); } } let gInstalledAddonId = ""; @@ -211,40 +215,111 @@ add_task(function* test_autoUpdatePrefPe Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE); Assert.ok(gPrefs.getBoolPref(autoupdateKey)); addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; Assert.ok(!gPrefs.prefHasUserValue(autoupdateKey)); } }); +function createMockPluginFilesIfNeeded(aFile, aPluginId) { + function createFile(aFileName) { + let f = aFile.clone(); + f.append(aFileName); + if (!f.exists()) { + f.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + } + }; + + // Note: we can't use Services.appInfo.OS, as that's "XPCShell" in our tests. + let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); + let isOSX = ("nsILocalFileMac" in Components.interfaces); + let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Components.classes); + + let libName = ""; + if (isWindows) { + libName = aPluginId.substring(4) + ".dll"; + } else if (isOSX) { + libName = "lib" + aPluginId.substring(4) + ".dylib"; + } else if (isLinux) { + libName = aPluginId.substring(4) + ".so"; + } else { + // FAIL! + return; + } + createFile(libName); + createFile(aPluginId.substring(4) + ".info"); +} + add_task(function* test_pluginRegistration() { const TEST_VERSION = "1.2.3.4"; + let profD = do_get_profile(); for (let addon of gMockAddons.values()) { - let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + let file = profD.clone(); file.append(addon.id); file.append(TEST_VERSION); let addedPaths = []; let removedPaths = []; let clearPaths = () => { addedPaths = []; removedPaths = []; } let MockGMPService = { - addPluginDirectory: path => addedPaths.push(path), - removePluginDirectory: path => removedPaths.push(path), - removeAndDeletePluginDirectory: path => removedPaths.push(path), + addPluginDirectory: path => { + if (!addedPaths.includes(path)) { + addedPaths.push(path); + } + }, + removePluginDirectory: path => { + if (!removedPaths.includes(path)) { + removedPaths.push(path); + } + }, + removeAndDeletePluginDirectory: path => { + if (!removedPaths.includes(path)) { + removedPaths.push(path); + } + }, + }; + + let reportedKeys = []; + + let MockTelemetry = { + getHistogramById: key => { + return { + add: value => { + reportedKeys.push(key); + } + } + } }; GMPScope.gmpService = MockGMPService; + GMPScope.telemetryService = MockTelemetry; gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true); - // Check that the plugin gets registered after startup. + // Test that plugin registration fails if the plugin dynamic library and + // info files are not present. gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id), - TEST_VERSION); + TEST_VERSION); + clearPaths(); + yield promiseRestartManager(); + Assert.equal(addedPaths.indexOf(file.path), -1); + Assert.deepEqual(removedPaths, [file.path]); + + // Test that the GMPProvider tried to report via telemetry that the + // addon's lib file is missing. + Assert.deepEqual(reportedKeys, [addon.missingKey]); + + // Create dummy GMP library/info files, and test that plugin registration + // succeeds during startup, now that we've added GMP info/lib files. + createMockPluginFilesIfNeeded(file, addon.id); + + gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id), + TEST_VERSION); clearPaths(); yield promiseRestartManager(); Assert.notEqual(addedPaths.indexOf(file.path), -1); Assert.deepEqual(removedPaths, []); // Check that clearing the version doesn't trigger registration. clearPaths(); gPrefs.clearUserPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id)); @@ -254,30 +329,30 @@ add_task(function* test_pluginRegistrati // Restarting with no version set should not trigger registration. clearPaths(); yield promiseRestartManager(); Assert.equal(addedPaths.indexOf(file.path), -1); Assert.equal(removedPaths.indexOf(file.path), -1); // Changing the pref mid-session should cause unregistration and registration. gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id), - TEST_VERSION); + TEST_VERSION); clearPaths(); const TEST_VERSION_2 = "5.6.7.8"; let file2 = Services.dirsvc.get("ProfD", Ci.nsIFile); file2.append(addon.id); file2.append(TEST_VERSION_2); gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id), TEST_VERSION_2); Assert.deepEqual(addedPaths, [file2.path]); Assert.deepEqual(removedPaths, [file.path]); // Disabling the plugin should cause unregistration. gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id), - TEST_VERSION); + TEST_VERSION); clearPaths(); gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), false); Assert.deepEqual(addedPaths, []); Assert.deepEqual(removedPaths, [file.path]); // Restarting with the plugin disabled should not cause registration. clearPaths(); yield promiseRestartManager();
--- a/webapprt/locales/en-US/webapprt/overrides/dom.properties +++ b/webapprt/locales/en-US/webapprt/overrides/dom.properties @@ -74,17 +74,16 @@ FullScreenDeniedHidden=Request for full- FullScreenDeniedIframeNotAllowed=Request for full-screen was denied because at least one of the document's containing iframes does not have an "allowfullscreen" attribute. FullScreenDeniedNotInputDriven=Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler. FullScreenDeniedNotInDocument=Request for full-screen was denied because requesting element is no longer in its document. FullScreenDeniedMovedDocument=Request for full-screen was denied because requesting element has moved document. FullScreenDeniedLostWindow=Request for full-screen was denied because we no longer have a window. FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen. FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element. FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab. -FullScreenDeniedContentOnly=Request for full-screen was denied because requesting element is in the chrome document and the fullscreen API is configured for content only. RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document. FocusedWindowedPluginWhileFullScreen=Exited full-screen because windowed plugin was focused. HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode. InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn't implement nsIWritablePropertyBag2. ResponseTypeSyncXHRWarning=Use of XMLHttpRequest's responseType attribute is no longer supported in the synchronous mode in window context. WithCredentialsSyncXHRWarning=Use of XMLHttpRequest's withCredentials attribute is no longer supported in the synchronous mode in window context. TimeoutSyncXHRWarning=Use of XMLHttpRequest's timeout attribute is not supported in the synchronous mode in window context. JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
--- a/widget/PuppetWidget.cpp +++ b/widget/PuppetWidget.cpp @@ -881,29 +881,22 @@ PuppetWidget::NotifyIMEOfSelectionChange #ifndef MOZ_CROSS_PROCESS_IME return NS_OK; #endif if (!mTabChild) return NS_ERROR_FAILURE; - nsEventStatus status; - WidgetQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, this); - InitEvent(queryEvent, nullptr); - DispatchEvent(&queryEvent, status); - - if (queryEvent.mSucceeded) { - mTabChild->SendNotifyIMESelection( - mIMELastReceivedSeqno, - queryEvent.GetSelectionStart(), - queryEvent.GetSelectionEnd(), - queryEvent.GetWritingMode(), - aIMENotification.mSelectionChangeData.mCausedByComposition); - } + mTabChild->SendNotifyIMESelection( + mIMELastReceivedSeqno, + aIMENotification.mSelectionChangeData.StartOffset(), + aIMENotification.mSelectionChangeData.EndOffset(), + aIMENotification.mSelectionChangeData.GetWritingMode(), + aIMENotification.mSelectionChangeData.mCausedByComposition); return NS_OK; } nsresult PuppetWidget::NotifyIMEOfMouseButtonEvent( const IMENotification& aIMENotification) { if (!mTabChild) {
--- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -531,16 +531,17 @@ public: NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT, "not querying selection"); return mReply.mOffset + (mReply.mReversed ? 0 : mReply.mString.Length()); } mozilla::WritingMode GetWritingMode(void) const { NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT || + message == NS_QUERY_CARET_RECT || message == NS_QUERY_TEXT_RECT, "not querying selection or text rect"); return mReply.mWritingMode; } bool mSucceeded; bool mWasAsync; bool mUseNativeLineBreak;
--- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -49,16 +49,17 @@ #include "mozilla/layers/APZEventState.h" #include "mozilla/layers/APZThreadUtils.h" #include "mozilla/layers/ChromeProcessController.h" #include "mozilla/layers/InputAPZContext.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "mozilla/dom/TabParent.h" #include "nsRefPtrHashtable.h" #include "TouchEvents.h" +#include "WritingModes.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif #ifdef DEBUG #include "nsIObserver.h" static void debug_RegisterPrefCallbacks(); @@ -93,16 +94,35 @@ bool gDisableNativeTheme #define TOUCH_INJECT_PUMP_TIMER_MSEC 50 #define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500 int32_t nsIWidget::sPointerIdCounter = 0; // Some statics from nsIWidget.h /*static*/ uint64_t AutoObserverNotifier::sObserverId = 0; /*static*/ nsDataHashtable<nsUint64HashKey, nsCOMPtr<nsIObserver>> AutoObserverNotifier::sSavedObservers; +namespace mozilla { +namespace widget { + +void +IMENotification::SelectionChangeData::SetWritingMode( + const WritingMode& aWritingMode) +{ + mWritingMode = aWritingMode.mWritingMode; +} + +WritingMode +IMENotification::SelectionChangeData::GetWritingMode() const +{ + return WritingMode(mWritingMode); +} + +} // namespace widget +} // namespace mozilla + nsAutoRollup::nsAutoRollup() { // remember if mLastRollup was null, and only clear it upon destruction // if so. This prevents recursive usage of nsAutoRollup from clearing // mLastRollup when it shouldn't. wasClear = !nsBaseWidget::mLastRollup; }
--- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -670,16 +670,20 @@ struct ParamTraits<mozilla::widget::IMEN typedef mozilla::widget::IMENotification paramType; static void Write(Message* aMsg, const paramType& aParam) { WriteParam(aMsg, static_cast<mozilla::widget::IMEMessageType>(aParam.mMessage)); switch (aParam.mMessage) { case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE: + WriteParam(aMsg, aParam.mSelectionChangeData.mOffset); + WriteParam(aMsg, aParam.mSelectionChangeData.mLength); + WriteParam(aMsg, aParam.mSelectionChangeData.mWritingMode); + WriteParam(aMsg, aParam.mSelectionChangeData.mReversed); WriteParam(aMsg, aParam.mSelectionChangeData.mCausedByComposition); return; case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE: WriteParam(aMsg, aParam.mTextChangeData.mStartOffset); WriteParam(aMsg, aParam.mTextChangeData.mOldEndOffset); WriteParam(aMsg, aParam.mTextChangeData.mNewEndOffset); WriteParam(aMsg, aParam.mTextChangeData.mCausedByComposition); return; @@ -706,16 +710,24 @@ struct ParamTraits<mozilla::widget::IMEN mozilla::widget::IMEMessageType IMEMessage = 0; if (!ReadParam(aMsg, aIter, &IMEMessage)) { return false; } aResult->mMessage = static_cast<mozilla::widget::IMEMessage>(IMEMessage); switch (aResult->mMessage) { case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE: return ReadParam(aMsg, aIter, + &aResult->mSelectionChangeData.mOffset) && + ReadParam(aMsg, aIter, + &aResult->mSelectionChangeData.mLength) && + ReadParam(aMsg, aIter, + &aResult->mSelectionChangeData.mWritingMode) && + ReadParam(aMsg, aIter, + &aResult->mSelectionChangeData.mReversed) && + ReadParam(aMsg, aIter, &aResult->mSelectionChangeData.mCausedByComposition); case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE: return ReadParam(aMsg, aIter, &aResult->mTextChangeData.mStartOffset) && ReadParam(aMsg, aIter, &aResult->mTextChangeData.mOldEndOffset) && ReadParam(aMsg, aIter, &aResult->mTextChangeData.mNewEndOffset) &&
--- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -33,16 +33,17 @@ class imgIContainer; class nsIContent; class ViewWrapper; class nsIWidgetListener; class nsIntRegion; class nsIScreen; namespace mozilla { class CompositorVsyncDispatcher; +class WritingMode; namespace dom { class TabChild; } namespace plugins { class PluginWidgetChild; } namespace layers { class Composer2D; @@ -590,16 +591,20 @@ struct IMENotification : mMessage(static_cast<IMEMessage>(-1)) {} MOZ_IMPLICIT IMENotification(IMEMessage aMessage) : mMessage(aMessage) { switch (aMessage) { case NOTIFY_IME_OF_SELECTION_CHANGE: + mSelectionChangeData.mOffset = UINT32_MAX; + mSelectionChangeData.mLength = 0; + mSelectionChangeData.mWritingMode = 0; + mSelectionChangeData.mReversed = false; mSelectionChangeData.mCausedByComposition = false; break; case NOTIFY_IME_OF_TEXT_CHANGE: mTextChangeData.mStartOffset = 0; mTextChangeData.mOldEndOffset = 0; mTextChangeData.mNewEndOffset = 0; mTextChangeData.mCausedByComposition = false; break; @@ -613,23 +618,50 @@ struct IMENotification mMouseButtonEventData.mModifiers = 0; default: break; } } IMEMessage mMessage; + // NOTIFY_IME_OF_SELECTION_CHANGE specific data + struct SelectionChangeData + { + // Selection range. + uint32_t mOffset; + uint32_t mLength; + + // Writing mode at the selection. + uint8_t mWritingMode; + + bool mReversed; + bool mCausedByComposition; + + void SetWritingMode(const WritingMode& aWritingMode); + WritingMode GetWritingMode() const; + + uint32_t StartOffset() const + { + return mOffset + (mReversed ? mLength : 0); + } + uint32_t EndOffset() const + { + return mOffset + (mReversed ? 0 : mLength); + } + bool IsInInt32Range() const + { + return mOffset + mLength <= INT32_MAX; + } + }; + union { // NOTIFY_IME_OF_SELECTION_CHANGE specific data - struct - { - bool mCausedByComposition; - } mSelectionChangeData; + SelectionChangeData mSelectionChangeData; // NOTIFY_IME_OF_TEXT_CHANGE specific data struct { uint32_t mStartOffset; uint32_t mOldEndOffset; uint32_t mNewEndOffset;
--- a/widget/windows/WinIMEHandler.cpp +++ b/widget/windows/WinIMEHandler.cpp @@ -175,16 +175,17 @@ IMEHandler::NotifyIME(nsWindow* aWindow, switch (aIMENotification.mMessage) { case NOTIFY_IME_OF_SELECTION_CHANGE: { nsresult rv = nsTextStore::OnSelectionChange(); // If IMM IME is active, we need to notify nsIMM32Handler of updating // composition change. It will adjust candidate window position or // composition window position. if (IsIMMActive()) { nsIMM32Handler::OnUpdateComposition(aWindow); + nsIMM32Handler::OnSelectionChange(aWindow, aIMENotification); } return rv; } case NOTIFY_IME_OF_COMPOSITION_UPDATE: // If IMM IME is active, we need to notify nsIMM32Handler of updating // composition change. It will adjust candidate window position or // composition window position. if (IsIMMActive()) { @@ -233,16 +234,19 @@ IMEHandler::NotifyIME(nsWindow* aWindow, return NS_OK; case REQUEST_TO_CANCEL_COMPOSITION: nsIMM32Handler::CancelComposition(aWindow); return NS_OK; case NOTIFY_IME_OF_POSITION_CHANGE: case NOTIFY_IME_OF_COMPOSITION_UPDATE: nsIMM32Handler::OnUpdateComposition(aWindow); return NS_OK; + case NOTIFY_IME_OF_SELECTION_CHANGE: + nsIMM32Handler::OnSelectionChange(aWindow, aIMENotification); + return NS_OK; case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: return nsIMM32Handler::OnMouseButtonEvent(aWindow, aIMENotification); #ifdef NS_ENABLE_TSF case NOTIFY_IME_OF_BLUR: // If a plugin gets focus while TSF has focus, we need to notify TSF of // the blur. if (nsTextStore::ThinksHavingFocus()) { return nsTextStore::OnFocusChange(false, aWindow,
--- a/widget/windows/nsIMM32Handler.cpp +++ b/widget/windows/nsIMM32Handler.cpp @@ -11,23 +11,113 @@ #include "nsWindowDefs.h" #include "WinUtils.h" #include "KeyboardLayout.h" #include <algorithm> #include "mozilla/MiscEvents.h" #include "mozilla/TextEvents.h" +#ifndef IME_PROP_ACCEPT_WIDE_VKEY +#define IME_PROP_ACCEPT_WIDE_VKEY 0x20 +#endif + using namespace mozilla; using namespace mozilla::widget; static nsIMM32Handler* gIMM32Handler = nullptr; PRLogModuleInfo* gIMM32Log = nullptr; +static void +HandleSeparator(nsACString& aDesc) +{ + if (!aDesc.IsEmpty()) { + aDesc.AppendLiteral(" | "); + } +} + +class GetIMEGeneralPropertyName : public nsAutoCString +{ +public: + GetIMEGeneralPropertyName(DWORD aFlags) + { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & IME_PROP_AT_CARET) { + AppendLiteral("IME_PROP_AT_CARET"); + } + if (aFlags & IME_PROP_SPECIAL_UI) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_SPECIAL_UI"); + } + if (aFlags & IME_PROP_CANDLIST_START_FROM_1) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_CANDLIST_START_FROM_1"); + } + if (aFlags & IME_PROP_UNICODE) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_UNICODE"); + } + if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT"); + } + if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY"); + } + } + virtual ~GetIMEGeneralPropertyName() {} +}; + +class GetIMEUIPropertyName : public nsAutoCString +{ +public: + GetIMEUIPropertyName(DWORD aFlags) + { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & UI_CAP_2700) { + AppendLiteral("UI_CAP_2700"); + } + if (aFlags & UI_CAP_ROT90) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROT90"); + } + if (aFlags & UI_CAP_ROTANY) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROTANY"); + } + } + virtual ~GetIMEUIPropertyName() {} +}; + +class GetWritingModeName : public nsAutoCString +{ +public: + GetWritingModeName(const WritingMode& aWritingMode) + { + if (!aWritingMode.IsVertical()) { + Assign("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + Assign("Vertical (LR)"); + return; + } + Assign("Vertical (RL)"); + } + virtual ~GetWritingModeName() {} +}; + static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000 //------------------------------------------------------------------------- // // from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h // The document for this has been removed from MSDN... // //------------------------------------------------------------------------- @@ -36,18 +126,22 @@ static UINT sWM_MSIME_MOUSE = 0; // mous #define IMEMOUSE_NONE 0x00 // no mouse button was pushed #define IMEMOUSE_LDOWN 0x01 #define IMEMOUSE_RDOWN 0x02 #define IMEMOUSE_MDOWN 0x04 #define IMEMOUSE_WUP 0x10 // wheel up #define IMEMOUSE_WDOWN 0x20 // wheel down +WritingMode nsIMM32Handler::sWritingModeOfCompositionFont; +nsString nsIMM32Handler::sIMEName; UINT nsIMM32Handler::sCodePage = 0; DWORD nsIMM32Handler::sIMEProperty = 0; +DWORD nsIMM32Handler::sIMEUIProperty = 0; +bool nsIMM32Handler::sAssumeVerticalWritingModeNotSupported = false; /* static */ void nsIMM32Handler::EnsureHandlerInstance() { if (!gIMM32Handler) { gIMM32Handler = new nsIMM32Handler(); } } @@ -56,17 +150,20 @@ nsIMM32Handler::EnsureHandlerInstance() nsIMM32Handler::Initialize() { if (!gIMM32Log) gIMM32Log = PR_NewLogModule("nsIMM32HandlerWidgets"); if (!sWM_MSIME_MOUSE) { sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE); } - InitKeyboardLayout(::GetKeyboardLayout(0)); + sAssumeVerticalWritingModeNotSupported = + Preferences::GetBool( + "intl.imm.vertical_writing.always_assume_not_supported", false); + InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0)); } /* static */ void nsIMM32Handler::Terminate() { if (!gIMM32Handler) return; delete gIMM32Handler; @@ -96,52 +193,118 @@ nsIMM32Handler::IsTopLevelWindowOfCompos { if (!gIMM32Handler || !gIMM32Handler->mComposingWindow) { return false; } HWND wnd = gIMM32Handler->mComposingWindow->GetWindowHandle(); return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle(); } +/* static */ +bool +nsIMM32Handler::IsJapanist2003Active() +{ + return sIMEName.EqualsLiteral("Japanist 2003"); +} + +/* static */ bool +nsIMM32Handler::IsGoogleJapaneseInputActive() +{ + // NOTE: Even on Windows for en-US, the name of Google Japanese Input is + // written in Japanese. + return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B " + L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB"); +} + /* static */ bool nsIMM32Handler::ShouldDrawCompositionStringOurselves() { // If current IME has special UI or its composition window should not // positioned to caret position, we should now draw composition string // ourselves. return !(sIMEProperty & IME_PROP_SPECIAL_UI) && (sIMEProperty & IME_PROP_AT_CARET); } +/* static */ bool +nsIMM32Handler::IsVerticalWritingSupported() +{ + // Even if IME claims that they support vertical writing mode but it may not + // support vertical writing mode for its candidate window. + if (sAssumeVerticalWritingModeNotSupported) { + return false; + } + // Google Japanese Input doesn't support vertical writing mode. We should + // return false if it's active IME. + if (IsGoogleJapaneseInputActive()) { + return false; + } + return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY)); +} + /* static */ void -nsIMM32Handler::InitKeyboardLayout(HKL aKeyboardLayout) +nsIMM32Handler::InitKeyboardLayout(nsWindow* aWindow, + HKL aKeyboardLayout) { + UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0); + if (IMENameLength) { + // Add room for the terminating null character + sIMEName.SetLength(++IMENameLength); + IMENameLength = + ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.BeginWriting(), + IMENameLength); + // Adjust the length to ignore the terminating null character + sIMEName.SetLength(IMENameLength); + } else { + sIMEName.Truncate(); + } + WORD langID = LOWORD(aKeyboardLayout); ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY); + sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI); + + // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API. + // For hacking some bugs of some TIP, we should set an IME name from the + // pref. + if (sCodePage == 932 && sIMEName.IsEmpty()) { + sIMEName = + Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as"); + } + + // Whether the IME supports vertical writing mode might be changed or + // some IMEs may need specific font for their UI. Therefore, we should + // update composition font forcibly here. + if (aWindow) { + MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true); + } + PR_LOG(gIMM32Log, PR_LOG_ALWAYS, - ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x, sCodePage=%lu, " - "sIMEProperty=%08x", - aKeyboardLayout, sCodePage, sIMEProperty)); + ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, " + "sIMEProperty=%s, sIMEUIProperty=%s", + aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), + sCodePage, GetIMEGeneralPropertyName(sIMEProperty).get(), + GetIMEUIPropertyName(sIMEUIProperty).get())); } /* static */ UINT nsIMM32Handler::GetKeyboardCodePage() { return sCodePage; } /* static */ nsIMEUpdatePreference nsIMM32Handler::GetIMEUpdatePreference() { return nsIMEUpdatePreference( nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE | + nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE | nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR); } // used for checking the lParam of WM_IME_COMPOSITION #define IS_COMPOSING_LPARAM(lParam) \ ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR) // Some IMEs (e.g., the standard IME for Korean) don't have caret position, @@ -253,30 +416,67 @@ nsIMM32Handler::OnUpdateComposition(nsWi if (aWindow->PluginHasFocus()) { return; } nsIMEContext IMEContext(aWindow->GetWindowHandle()); gIMM32Handler->SetIMERelatedWindowsPos(aWindow, IMEContext); } +// static +void +nsIMM32Handler::OnSelectionChange(nsWindow* aWindow, + const IMENotification& aIMENotification) +{ + if (aIMENotification.mSelectionChangeData.mCausedByComposition) { + return; + } + MaybeAdjustCompositionFont(aWindow, + aIMENotification.mSelectionChangeData.GetWritingMode()); +} + +// static +void +nsIMM32Handler::MaybeAdjustCompositionFont(nsWindow* aWindow, + const WritingMode& aWritingMode, + bool aForceUpdate) +{ + switch (sCodePage) { + case 932: // Japanese Shift-JIS + case 936: // Simlified Chinese GBK + case 949: // Korean + case 950: // Traditional Chinese Big5 + EnsureHandlerInstance(); + break; + default: + // If there is no instance of nsIMM32Hander, we shouldn't waste footprint. + if (!gIMM32Handler) { + return; + } + } + + // Like Navi-Bar of ATOK, some IMEs may require proper composition font even + // before sending WM_IME_STARTCOMPOSITION. + nsIMEContext IMEContext(aWindow->GetWindowHandle()); + gIMM32Handler->AdjustCompositionFont(IMEContext, aWritingMode, aForceUpdate); +} /* static */ bool nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, MSGResult& aResult) { aResult.mResult = 0; aResult.mConsumed = false; // We don't need to create the instance of the handler here. if (gIMM32Handler) { gIMM32Handler->OnInputLangChange(aWindow, wParam, lParam, aResult); } - InitKeyboardLayout(reinterpret_cast<HKL>(lParam)); + InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam)); // We can release the instance here, because the instance may be never // used. E.g., the new keyboard layout may not use IME, or it may use TSF. Terminate(); // Don't return as "processed", the messages should be processed on nsWindow // too. return false; } @@ -760,16 +960,19 @@ nsIMM32Handler::OnIMEStartCompositionOnP { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n", aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE")); mIsComposingOnPlugin = true; mComposingWindow = aWindow; nsIMEContext IMEContext(aWindow->GetWindowHandle()); SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext); + // On widnowless plugin, we should assume that the focused editor is always + // in horizontal writing mode. + AdjustCompositionFont(IMEContext, WritingMode()); aResult.mConsumed = aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam, false); return true; } bool nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow, @@ -936,16 +1139,18 @@ nsIMM32Handler::HandleStartComposition(n aWindow->InitEvent(selection, &point); aWindow->DispatchWindowEvent(&selection); if (!selection.mSucceeded) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n")); return; } + AdjustCompositionFont(aIMEContext, selection.GetWritingMode()); + mCompositionStart = selection.mReply.mOffset; WidgetCompositionEvent event(true, NS_COMPOSITION_START, aWindow); aWindow->InitEvent(event, &point); aWindow->DispatchWindowEvent(&event); mIsComposing = true; mComposingWindow = aWindow; @@ -1346,28 +1551,56 @@ nsIMM32Handler::HandleQueryCharPosition( GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r); NS_ENSURE_TRUE(ret, false); nsIntRect screenRect; // We always need top level window that is owner window of the popup window // even if the content of the popup window has focus. ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect); + + // XXX This might need to check writing mode. However, MSDN doesn't explain + // how to set the values in vertical writing mode. Additionally, IME + // doesn't work well with top-left of the character (this is explicitly + // documented) and its horizontal width. So, it might be better to set + // top-right corner of the character and horizontal width, but we're not + // sure if it doesn't cause any problems with a lot of IMEs... pCharPosition->pt.x = screenRect.x; pCharPosition->pt.y = screenRect.y; pCharPosition->cLineHeight = r.height; - // XXX we should use NS_QUERY_EDITOR_RECT event here. - ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); + WidgetQueryContentEvent editorRect(true, NS_QUERY_EDITOR_RECT, aWindow); + aWindow->InitEvent(editorRect); + aWindow->DispatchWindowEvent(&editorRect); + if (NS_WARN_IF(!editorRect.mSucceeded)) { + PR_LOG(gIMM32Log, PR_LOG_ERROR, + ("IMM32: HandleQueryCharPosition, NS_QUERY_EDITOR_RECT failed")); + ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); + } else { + nsIntRect editorRectInWindow = + LayoutDevicePixel::ToUntyped(editorRect.mReply.mRect); + nsWindow* window = editorRect.mReply.mFocusedWidget ? + static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget) : aWindow; + nsIntRect editorRectInScreen; + ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen); + ::SetRect(&pCharPosition->rcDocument, + editorRectInScreen.x, editorRectInScreen.y, + editorRectInScreen.XMost(), editorRectInScreen.YMost()); + } *oResult = TRUE; PR_LOG(gIMM32Log, PR_LOG_ALWAYS, - ("IMM32: HandleQueryCharPosition, SUCCEEDED\n")); + ("IMM32: HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, " + "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, " + "bottom=%d } }", + pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight, + pCharPosition->rcDocument.left, pCharPosition->rcDocument.top, + pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom)); return true; } bool nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult) { @@ -1756,17 +1989,18 @@ nsIMM32Handler::ConvertToANSIString(cons ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(), (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr); return true; } bool nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, uint32_t aOffset, - nsIntRect &aCharRect) + nsIntRect& aCharRect, + WritingMode* aWritingMode) { nsIntPoint point(0, 0); WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); aWindow->InitEvent(selection, &point); aWindow->DispatchWindowEvent(&selection); if (!selection.mSucceeded) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, @@ -1793,31 +2027,39 @@ nsIMM32Handler::GetCharacterRectOfSelect nsIntRect r; if (!useCaretRect) { WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, aWindow); charRect.InitForQueryTextRect(offset, 1); aWindow->InitEvent(charRect, &point); aWindow->DispatchWindowEvent(&charRect); if (charRect.mSucceeded) { aCharRect = LayoutDevicePixel::ToUntyped(charRect.mReply.mRect); + if (aWritingMode) { + *aWritingMode = charRect.GetWritingMode(); + } PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n", aOffset)); PR_LOG(gIMM32Log, PR_LOG_ALWAYS, - ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", - aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height)); + ("IMM32: GetCharacterRectOfSelectedTextAt, " + "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "charRect.GetWritingMode()=%s", + aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height, + GetWritingModeName(charRect.GetWritingMode()).get())); return true; } } - return GetCaretRect(aWindow, aCharRect); + return GetCaretRect(aWindow, aCharRect, aWritingMode); } bool -nsIMM32Handler::GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect) +nsIMM32Handler::GetCaretRect(nsWindow* aWindow, + nsIntRect& aCaretRect, + WritingMode* aWritingMode) { nsIntPoint point(0, 0); WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow); aWindow->InitEvent(selection, &point); aWindow->DispatchWindowEvent(&selection); if (!selection.mSucceeded) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, @@ -1832,30 +2074,37 @@ nsIMM32Handler::GetCaretRect(nsWindow* a aWindow->InitEvent(caretRect, &point); aWindow->DispatchWindowEvent(&caretRect); if (!caretRect.mSucceeded) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: GetCaretRect, FAILED (NS_QUERY_CARET_RECT)\n")); return false; } aCaretRect = LayoutDevicePixel::ToUntyped(caretRect.mReply.mRect); + if (aWritingMode) { + *aWritingMode = caretRect.GetWritingMode(); + } PR_LOG(gIMM32Log, PR_LOG_ALWAYS, - ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n", - aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height)); + ("IMM32: GetCaretRect, SUCCEEDED, " + "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "caretRect.GetWritingMode()=%s", + aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height, + GetWritingModeName(caretRect.GetWritingMode()).get())); return true; } bool nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow, const nsIMEContext &aIMEContext) { nsIntRect r; // Get first character rect of current a normal selected text or a composing // string. - bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); + WritingMode writingMode; + bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode); NS_ENSURE_TRUE(ret, false); nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); nsIntRect firstSelectedCharRect; ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect); // Set native caret size/position to our caret. Some IMEs honor it. E.g., // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified // Chinese) on XP. @@ -1876,59 +2125,117 @@ nsIMM32Handler::SetIMERelatedWindowsPos( } ::SetCaretPos(caretRect.x, caretRect.y); if (ShouldDrawCompositionStringOurselves()) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n")); // Get a rect of first character in current target in composition string. + nsIntRect firstTargetCharRect, lastTargetCharRect; if (mIsComposing && !mCompositionString.IsEmpty()) { // If there are no targetted selection, we should use it's first character // rect instead. - uint32_t offset; - if (!GetTargetClauseRange(&offset)) { + uint32_t offset, length; + if (!GetTargetClauseRange(&offset, &length)) { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n")); return false; } ret = GetCharacterRectOfSelectedTextAt(aWindow, - offset - mCompositionStart, r); + offset - mCompositionStart, + firstTargetCharRect, &writingMode); NS_ENSURE_TRUE(ret, false); + if (length) { + ret = GetCharacterRectOfSelectedTextAt(aWindow, + offset + length - 1 - mCompositionStart, lastTargetCharRect); + NS_ENSURE_TRUE(ret, false); + } else { + lastTargetCharRect = firstTargetCharRect; + } } else { // If there are no composition string, we should use a first character // rect. - ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r); + ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, + firstTargetCharRect, &writingMode); NS_ENSURE_TRUE(ret, false); + lastTargetCharRect = firstTargetCharRect; } - nsIntRect firstTargetCharRect; - ResolveIMECaretPos(toplevelWindow, r, aWindow, firstTargetCharRect); + ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, + aWindow, firstTargetCharRect); + ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, + aWindow, lastTargetCharRect); + nsIntRect targetClauseRect; + targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect); - // Move the candidate window to first character position of the target. + // Move the candidate window to proper position from the target clause as + // far as possible. CANDIDATEFORM candForm; candForm.dwIndex = 0; - candForm.dwStyle = CFS_EXCLUDE; - candForm.ptCurrentPos.x = firstTargetCharRect.x; - candForm.ptCurrentPos.y = firstTargetCharRect.y; - candForm.rcArea.right = candForm.rcArea.left = candForm.ptCurrentPos.x; - candForm.rcArea.top = candForm.ptCurrentPos.y; - candForm.rcArea.bottom = candForm.ptCurrentPos.y + - firstTargetCharRect.height; + if (!writingMode.IsVertical() || IsVerticalWritingSupported()) { + candForm.dwStyle = CFS_EXCLUDE; + // Candidate window shouldn't overlap the target clause in any writing + // mode. + candForm.rcArea.left = targetClauseRect.x; + candForm.rcArea.right = targetClauseRect.XMost(); + candForm.rcArea.top = targetClauseRect.y; + candForm.rcArea.bottom = targetClauseRect.YMost(); + if (!writingMode.IsVertical()) { + // In horizontal layout, current point of interest should be top-left + // of the first character. + candForm.ptCurrentPos.x = firstTargetCharRect.x; + candForm.ptCurrentPos.y = firstTargetCharRect.y; + } else if (writingMode.IsVerticalRL()) { + // In vertical layout (RL), candidate window should be positioned right + // side of target clause. However, we don't set vertical writing font + // to the IME. Therefore, the candidate window may be positioned + // bottom-left of target clause rect with these information. + candForm.ptCurrentPos.x = targetClauseRect.x; + candForm.ptCurrentPos.y = targetClauseRect.y; + } else { + MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?"); + // In vertical layout (LR), candidate window should be poisitioned left + // side of target clause. Although, we don't set vertical writing font + // to the IME, the candidate window may be positioned bottom-right of + // the target clause rect with these information. + candForm.ptCurrentPos.x = targetClauseRect.XMost(); + candForm.ptCurrentPos.y = targetClauseRect.y; + } + } else { + // If vertical writing is not supported by IME, let's set candidate + // window position to the bottom-left of the target clause because + // the position must be the safest position to prevent the candidate + // window to overlap with the target clause. + candForm.dwStyle = CFS_CANDIDATEPOS; + candForm.ptCurrentPos.x = targetClauseRect.x; + candForm.ptCurrentPos.y = targetClauseRect.YMost(); + } + PR_LOG(gIMM32Log, PR_LOG_ALWAYS, + ("IMM32: SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... " + "ptCurrentPos={ x=%d, y=%d }, " + "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, " + "writingMode=%s", + candForm.ptCurrentPos.x, candForm.ptCurrentPos.y, + candForm.rcArea.left, candForm.rcArea.top, + candForm.rcArea.right, candForm.rcArea.bottom, + GetWritingModeName(writingMode).get())); ::ImmSetCandidateWindow(aIMEContext.get(), &candForm); } else { PR_LOG(gIMM32Log, PR_LOG_ALWAYS, ("IMM32: SetIMERelatedWindowsPos, Set composition window\n")); // Move the composition window to caret position (if selected some // characters, we should use first character rect of them). // And in this mode, IME adjusts the candidate window position // automatically. So, we don't need to set it. COMPOSITIONFORM compForm; compForm.dwStyle = CFS_POINT; - compForm.ptCurrentPos.x = firstSelectedCharRect.x; + compForm.ptCurrentPos.x = + !writingMode.IsVerticalLR() ? firstSelectedCharRect.x : + firstSelectedCharRect.XMost(); compForm.ptCurrentPos.y = firstSelectedCharRect.y; ::ImmSetCompositionWindow(aIMEContext.get(), &compForm); } return true; } void @@ -2006,16 +2313,148 @@ nsIMM32Handler::ResolveIMECaretPos(nsIWi if (aReferenceWidget) aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffsetUntyped()); if (aNewOriginWidget) aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffsetUntyped()); } +static void +SetHorizontalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) +{ + aLogFont.lfEscapement = aLogFont.lfOrientation = 0; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) { + memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System")); + return; + } + memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length()] = 0; +} + +static void +SetVerticalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) +{ + aLogFont.lfEscapement = aLogFont.lfOrientation = 2700; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) { + memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System")); + return; + } + aLogFont.lfFaceName[0] = '@'; + memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length() + 1] = 0; +} + +void +nsIMM32Handler::AdjustCompositionFont(const nsIMEContext& aIMEContext, + const WritingMode& aWritingMode, + bool aForceUpdate) +{ + // An instance of nsIMM32Handler is destroyed when active IME is changed. + // Therefore, we need to store the information which are set to the IM + // context to static variables since IM context is never recreated. + static bool sCompositionFontsInitialized = false; + static nsString sCompositionFont = + Preferences::GetString("intl.imm.composition_font"); + + // If composition font is customized by pref, we need to modify the + // composition font of the IME context at first time even if the writing mode + // is horizontal. + bool setCompositionFontForcibly = aForceUpdate || + (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty()); + + static WritingMode sCurrentWritingMode; + static nsString sCurrentIMEName; + if (!setCompositionFontForcibly && + sWritingModeOfCompositionFont == aWritingMode && + sCurrentIMEName == sIMEName) { + // Nothing to do if writing mode isn't being changed. + return; + } + + // Decide composition fonts for both horizontal writing mode and vertical + // writing mode. If the font isn't specified by the pref, use default + // font which is already set to the IM context. And also in vertical writing + // mode, insert '@' to the start of the font. + if (!sCompositionFontsInitialized) { + sCompositionFontsInitialized = true; + // sCompositionFontH must not start with '@' and its length is less than + // LF_FACESIZE since it needs to end with null terminating character. + if (sCompositionFont.IsEmpty() || + sCompositionFont.Length() > LF_FACESIZE - 1 || + sCompositionFont[0] == '@') { + LOGFONTW defaultLogFont; + if (NS_WARN_IF(!::ImmGetCompositionFont(aIMEContext.get(), + &defaultLogFont))) { + PR_LOG(gIMM32Log, PR_LOG_ERROR, + ("IMM32: AdjustCompositionFont, ::ImmGetCompositionFont() failed")); + sCompositionFont.AssignLiteral("System"); + } else { + // The font face is typically, "System". + sCompositionFont.Assign(defaultLogFont.lfFaceName); + } + } + + PR_LOG(gIMM32Log, PR_LOG_ALWAYS, + ("IMM32: AdjustCompositionFont, sCompositionFont=\"%s\" is initialized", + NS_ConvertUTF16toUTF8(sCompositionFont).get())); + } + + static nsString sCompositionFontForJapanist2003; + if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) { + const char* kCompositionFontForJapanist2003 = + "intl.imm.composition_font.japanist_2003"; + sCompositionFontForJapanist2003 = + Preferences::GetString(kCompositionFontForJapanist2003); + // If the font name is not specified properly, let's use + // "MS PGothic" instead. + if (sCompositionFontForJapanist2003.IsEmpty() || + sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 || + sCompositionFontForJapanist2003[0] == '@') { + sCompositionFontForJapanist2003.AssignLiteral("MS PGothic"); + } + } + + sWritingModeOfCompositionFont = aWritingMode; + sCurrentIMEName = sIMEName; + + LOGFONTW logFont; + memset(&logFont, 0, sizeof(logFont)); + if (!::ImmGetCompositionFont(aIMEContext.get(), &logFont)) { + PR_LOG(gIMM32Log, PR_LOG_ERROR, + ("IMM32: AdjustCompositionFont, ::ImmGetCompositionFont() failed")); + logFont.lfFaceName[0] = 0; + } + // Need to reset some information which should be recomputed with new font. + logFont.lfWidth = 0; + logFont.lfWeight = FW_DONTCARE; + logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + logFont.lfPitchAndFamily = DEFAULT_PITCH; + + if (!mIsComposingOnPlugin && + aWritingMode.IsVertical() && IsVerticalWritingSupported()) { + SetVerticalFontToLogFont( + IsJapanist2003Active() ? sCompositionFontForJapanist2003 : + sCompositionFont, logFont); + } else { + SetHorizontalFontToLogFont( + IsJapanist2003Active() ? sCompositionFontForJapanist2003 : + sCompositionFont, logFont); + } + PR_LOG(gIMM32Log, PR_LOG_WARNING, + ("IMM32: AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")", + NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get())); + ::ImmSetCompositionFontW(aIMEContext.get(), &logFont); +} + /* static */ nsresult nsIMM32Handler::OnMouseButtonEvent(nsWindow* aWindow, const IMENotification& aIMENotification) { // We don't need to create the instance of the handler here. if (!gIMM32Handler) { return NS_OK; }
--- a/widget/windows/nsIMM32Handler.h +++ b/widget/windows/nsIMM32Handler.h @@ -9,16 +9,17 @@ #include "nscore.h" #include <windows.h> #include "nsCOMPtr.h" #include "nsString.h" #include "nsTArray.h" #include "nsIWidget.h" #include "mozilla/EventForwards.h" #include "nsRect.h" +#include "WritingModes.h" class nsWindow; namespace mozilla { namespace widget { struct MSGResult; @@ -138,33 +139,40 @@ public: #endif // If aForce is TRUE, these methods doesn't check whether we have composition // or not. If you don't set it to TRUE, these method doesn't commit/cancel // the composition on uexpected window. static void CommitComposition(nsWindow* aWindow, bool aForce = false); static void CancelComposition(nsWindow* aWindow, bool aForce = false); static void OnUpdateComposition(nsWindow* aWindow); + static void OnSelectionChange(nsWindow* aWindow, + const IMENotification& aIMENotification); static nsIMEUpdatePreference GetIMEUpdatePreference(); // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by // IME. Otherwise, NS_OK. static nsresult OnMouseButtonEvent(nsWindow* aWindow, const IMENotification& aIMENotification); protected: static void EnsureHandlerInstance(); static bool IsComposingOnOurEditor(); static bool IsComposingOnPlugin(); static bool IsComposingWindow(nsWindow* aWindow); + static bool IsJapanist2003Active(); + static bool IsGoogleJapaneseInputActive(); + static bool ShouldDrawCompositionStringOurselves(); - static void InitKeyboardLayout(HKL aKeyboardLayout); + static bool IsVerticalWritingSupported(); + // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE. + static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout); static UINT GetKeyboardCodePage(); /** * Checks whether the window is top level window of the composing window. * In this method, the top level window means in all windows, not only in all * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. */ static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); @@ -270,23 +278,47 @@ protected: bool ConvertToANSIString(const nsAFlatString& aStr, UINT aCodePage, nsACString& aANSIStr); bool SetIMERelatedWindowsPos(nsWindow* aWindow, const nsIMEContext& aIMEContext); void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, const nsIMEContext& aIMEContext); - bool GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, - uint32_t aOffset, - nsIntRect &aCharRect); - bool GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect); + bool GetCharacterRectOfSelectedTextAt( + nsWindow* aWindow, + uint32_t aOffset, + nsIntRect& aCharRect, + mozilla::WritingMode* aWritingMode = nullptr); + bool GetCaretRect(nsWindow* aWindow, + nsIntRect& aCaretRect, + mozilla::WritingMode* aWritingMode = nullptr); void GetCompositionString(const nsIMEContext &aIMEContext, DWORD aIndex, nsAString& aCompositionString) const; + + /** + * AdjustCompositionFont() makes IME vertical writing mode if it's supported. + * If aForceUpdate is true, it will update composition font even if writing + * mode isn't being changed. + */ + void AdjustCompositionFont(const nsIMEContext& aIMEContext, + const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + + /** + * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the + * locale of active IME is CJK. Note that this creates an instance even + * when there is no composition but the locale is CJK. + */ + static void MaybeAdjustCompositionFont( + nsWindow* aWindow, + const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + /** * Get the current target clause of composition string. * If there are one or more characters whose attribute is ATTR_TARGET_*, * this returns the first character's offset and its length. * Otherwise, e.g., the all characters are ATTR_INPUT, this returns * the composition string range because the all is the current target. * * aLength can be null (default), but aOffset must not be null. @@ -354,13 +386,17 @@ protected: int32_t mCursorPosition; uint32_t mCompositionStart; bool mIsComposing; bool mIsComposingOnPlugin; bool mNativeCaretIsCreated; + static mozilla::WritingMode sWritingModeOfCompositionFont; + static nsString sIMEName; static UINT sCodePage; static DWORD sIMEProperty; + static DWORD sIMEUIProperty; + static bool sAssumeVerticalWritingModeNotSupported; }; #endif // nsIMM32Handler_h__