author | Ms2ger <ms2ger@gmail.com> |
Thu, 15 Nov 2012 10:14:44 +0100 | |
changeset 113367 | 92a92a0d4f9d29109b54a00ec29c85c0182904f2 |
parent 113310 | 4df2bf1d77e93eecfb7a0183bdd58641e691b20a (current diff) |
parent 113366 | a9500b386854cf11cf9ffed05dc1921386bdb61d (diff) |
child 113368 | 31fdd0d6bdd386c139e5f79d6599745e75862a09 |
push id | 23869 |
push user | emorley@mozilla.com |
push date | Thu, 15 Nov 2012 16:18:16 +0000 |
treeherder | mozilla-central@a37525d304d9 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 19.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/src/base/AccEvent.cpp +++ b/accessible/src/base/AccEvent.cpp @@ -85,17 +85,17 @@ AccEvent::CreateXPCOMObject() } //////////////////////////////////////////////////////////////////////////////// // AccEvent cycle collection NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(AccEvent) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mAccessible) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(AccEvent) NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAccessible"); cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mAccessible)); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef)
--- a/accessible/src/base/NotificationController.cpp +++ b/accessible/src/base/NotificationController.cpp @@ -63,21 +63,19 @@ NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(No NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController) if (tmp->mDocument) tmp->Shutdown(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController) NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument"); cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mDocument.get())); - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mHangingChildDocuments, - DocAccessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mContentInsertions, - ContentInsertion) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mEvents, AccEvent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release) //////////////////////////////////////////////////////////////////////////////// // NotificationCollector: public @@ -865,17 +863,17 @@ NotificationController::ContentInsertion } return haveToUpdate; } NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController::ContentInsertion) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController::ContentInsertion) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContainer) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContainer) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController::ContentInsertion) NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContainer"); cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mContainer.get())); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController::ContentInsertion,
--- a/accessible/src/base/nsAccessiblePivot.cpp +++ b/accessible/src/base/nsAccessiblePivot.cpp @@ -52,28 +52,28 @@ nsAccessiblePivot::nsAccessiblePivot(Acc } //////////////////////////////////////////////////////////////////////////////// // nsISupports NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessiblePivot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccessiblePivot) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRoot, nsIAccessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mPosition, nsIAccessible) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPosition) uint32_t i, length = tmp->mObservers.Length(); for (i = 0; i < length; ++i) { cb.NoteXPCOMChild(tmp->mObservers[i]); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccessiblePivot) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPosition) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mObservers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPosition) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot) NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot) NS_INTERFACE_MAP_ENTRY(nsAccessiblePivot) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot) NS_INTERFACE_MAP_END
--- a/accessible/src/generic/Accessible.cpp +++ b/accessible/src/generic/Accessible.cpp @@ -93,18 +93,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ uint32_t i, length = tmp->mChildren.Length(); for (i = 0; i < length; ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildren[i]"); cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mChildren[i].get())); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Accessible, nsAccessNode) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParent) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildren) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(Accessible, nsAccessNode) NS_IMPL_RELEASE_INHERITED(Accessible, nsAccessNode) nsresult Accessible::QueryInterface(REFNSIID aIID, void** aInstancePtr) {
--- a/accessible/src/generic/DocAccessible.cpp +++ b/accessible/src/generic/DocAccessible.cpp @@ -118,39 +118,36 @@ DocAccessible::~DocAccessible() //////////////////////////////////////////////////////////////////////////////// // nsISupports NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNotificationController, - NotificationController) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController) if (tmp->mVirtualCursor) { - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mVirtualCursor, - nsAccessiblePivot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor) } uint32_t i, length = tmp->mChildDocuments.Length(); for (i = 0; i < length; ++i) { - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mChildDocuments[i], - nsIAccessible) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments[i]) } CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNotificationController) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mVirtualCursor) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments) tmp->mDependentIDsHash.Clear(); tmp->mNodeToAccessibleMap.Clear(); ClearCache(tmp->mAccessibleCache); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible) NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument) NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
--- a/accessible/src/xul/XULTreeAccessible.cpp +++ b/accessible/src/xul/XULTreeAccessible.cpp @@ -60,22 +60,22 @@ XULTreeAccessible:: //////////////////////////////////////////////////////////////////////////////// // XULTreeAccessible: nsISupports and cycle collection implementation NS_IMPL_CYCLE_COLLECTION_CLASS(XULTreeAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULTreeAccessible, Accessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTree) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTree) CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULTreeAccessible, Accessible) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTree) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTree) ClearCache(tmp->mAccessibleCache); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeAccessible) NS_INTERFACE_MAP_END_INHERITING(Accessible) NS_IMPL_ADDREF_INHERITED(XULTreeAccessible, Accessible) NS_IMPL_RELEASE_INHERITED(XULTreeAccessible, Accessible) @@ -699,22 +699,22 @@ XULTreeItemAccessibleBase:: //////////////////////////////////////////////////////////////////////////////// // XULTreeItemAccessibleBase: nsISupports implementation NS_IMPL_CYCLE_COLLECTION_CLASS(XULTreeItemAccessibleBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULTreeItemAccessibleBase, Accessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTree) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTree) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULTreeItemAccessibleBase, Accessible) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTree) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTree) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase) NS_INTERFACE_TABLE_INHERITED1(XULTreeItemAccessibleBase, XULTreeItemAccessibleBase) NS_INTERFACE_TABLE_TAIL_INHERITING(Accessible) NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessibleBase, Accessible) NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessibleBase, Accessible) @@ -1103,22 +1103,22 @@ XULTreeItemAccessible:: //////////////////////////////////////////////////////////////////////////////// // XULTreeItemAccessible: nsISupports implementation NS_IMPL_CYCLE_COLLECTION_CLASS(XULTreeItemAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mColumn) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mColumn) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mColumn) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mColumn) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessible) NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase) NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase) ////////////////////////////////////////////////////////////////////////////////
--- a/accessible/src/xul/XULTreeGridAccessible.cpp +++ b/accessible/src/xul/XULTreeGridAccessible.cpp @@ -473,24 +473,24 @@ XULTreeGridCellAccessible:: //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: nsISupports implementation NS_IMPL_CYCLE_COLLECTION_CLASS(XULTreeGridCellAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULTreeGridCellAccessible, LeafAccessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTree) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mColumn) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTree) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mColumn) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULTreeGridCellAccessible, LeafAccessible) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTree) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mColumn) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTree) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mColumn) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible) NS_INTERFACE_TABLE_INHERITED2(XULTreeGridCellAccessible, nsIAccessibleTableCell, XULTreeGridCellAccessible) NS_INTERFACE_TABLE_TAIL_INHERITING(LeafAccessible) NS_IMPL_ADDREF_INHERITED(XULTreeGridCellAccessible, LeafAccessible)
--- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -7,16 +7,17 @@ const PANEL_MIN_HEIGHT = 100; const PANEL_MIN_WIDTH = 330; let SocialUI = { // Called on delayed startup to initialize UI init: function SocialUI_init() { Services.obs.addObserver(this, "social:pref-changed", false); Services.obs.addObserver(this, "social:ambient-notification-changed", false); Services.obs.addObserver(this, "social:profile-changed", false); + Services.obs.addObserver(this, "social:recommend-info-changed", false); Services.obs.addObserver(this, "social:frameworker-error", false); Services.prefs.addObserver("social.sidebar.open", this, false); Services.prefs.addObserver("social.toast-notifications.enabled", this, false); gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler, true, true); // Called when we enter DOM full-screen mode. @@ -28,16 +29,17 @@ let SocialUI = { Social.init(this._providerReady.bind(this)); }, // Called on window unload uninit: function SocialUI_uninit() { Services.obs.removeObserver(this, "social:pref-changed"); Services.obs.removeObserver(this, "social:ambient-notification-changed"); Services.obs.removeObserver(this, "social:profile-changed"); + Services.obs.removeObserver(this, "social:recommend-info-changed"); Services.obs.removeObserver(this, "social:frameworker-error"); Services.prefs.removeObserver("social.sidebar.open", this); Services.prefs.removeObserver("social.toast-notifications.enabled", this); }, showProfile: function SocialUI_showProfile() { if (this.haveLoggedInUser()) @@ -69,16 +71,19 @@ let SocialUI = { SocialToolbar.updateButton(); SocialMenu.populate(); break; case "social:profile-changed": SocialToolbar.updateProfile(); SocialShareButton.updateProfileInfo(); SocialChatBar.update(); break; + case "social:recommend-info-changed": + SocialShareButton.updateShareState(); + break; case "social:frameworker-error": if (Social.provider) { Social.errorState = "frameworker-error"; SocialSidebar.setSidebarErrorMessage("frameworker-error"); } break; case "nsPref:changed": SocialSidebar.update(); @@ -249,18 +254,20 @@ let SocialChatBar = { update: function() { if (!this.isAvailable) this.chatbar.removeAll(); else { this.chatbar.hidden = document.mozFullScreen; } }, focus: function SocialChatBar_focus() { + if (!this.chatbar.selectedChat) + return; let commandDispatcher = gBrowser.ownerDocument.commandDispatcher; - commandDispatcher.advanceFocusIntoSubtree(this.chatbar); + commandDispatcher.advanceFocusIntoSubtree(this.chatbar.selectedChat); } } function sizeSocialPanelToContent(panel, iframe) { // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here? let doc = iframe.contentDocument; if (!doc || !doc.body) { return; @@ -448,101 +455,37 @@ let SocialFlyout = { panel.openPopup(anchor, "start_before", 0, yOffset, false, false); this.setUpProgressListener(); } this.yOffset = yOffset; } } let SocialShareButton = { - // promptImages and promptMessages being null means we are yet to get the - // message back from the provider with the images and icons (or that we got - // the response but determined it was invalid.) - promptImages: null, - promptMessages: null, - // Called once, after window load, when the Social.provider object is initialized init: function SSB_init() { this.updateButtonHiddenState(); this.updateProfileInfo(); }, updateProfileInfo: function SSB_updateProfileInfo() { let profileRow = document.getElementById("unsharePopupHeader"); let profile = Social.provider.profile; - this.promptImages = null; - this.promptMessages = null; if (profile && profile.displayName) { profileRow.hidden = false; let portrait = document.getElementById("socialUserPortrait"); portrait.setAttribute("src", profile.portrait || "chrome://global/skin/icons/information-32.png"); let displayName = document.getElementById("socialUserDisplayName"); displayName.setAttribute("label", profile.displayName); } else { profileRow.hidden = true; this.updateButtonHiddenState(); return; } - // XXX - this shouldn't be done as part of updateProfileInfo, but instead - // whenever we notice the provider has changed - but the concept of - // "provider changed" will only exist once bug 774520 lands. - // get the recommend-prompt info. - let port = Social.provider.getWorkerPort(); - if (port) { - port.onmessage = function(evt) { - if (evt.data.topic == "social.user-recommend-prompt-response") { - port.close(); - this.acceptRecommendInfo(evt.data.data); - this.updateButtonHiddenState(); - this.updateShareState(); - } - }.bind(this); - port.postMessage({topic: "social.user-recommend-prompt"}); - } - }, - - acceptRecommendInfo: function SSB_acceptRecommendInfo(data) { - // Accept *and validate* the user-recommend-prompt-response message. - let promptImages = {}; - let promptMessages = {}; - function reportError(reason) { - Cu.reportError("Invalid recommend data from provider: " + reason + ": sharing is disabled for this provider"); - return false; - } - if (!data || - !data.images || typeof data.images != "object" || - !data.messages || typeof data.messages != "object") { - return reportError("data is missing valid 'images' or 'messages' elements"); - } - for (let sub of ["share", "unshare"]) { - let url = data.images[sub]; - if (!url || typeof url != "string" || url.length == 0) { - return reportError('images["' + sub + '"] is missing or not a non-empty string'); - } - // resolve potentially relative URLs then check the scheme is acceptable. - url = Services.io.newURI(Social.provider.origin, null, null).resolve(url); - let uri = Services.io.newURI(url, null, null); - if (!uri.schemeIs("http") && !uri.schemeIs("https") && !uri.schemeIs("data")) { - return reportError('images["' + sub + '"] does not have a valid scheme'); - } - promptImages[sub] = url; - } - for (let sub of ["shareTooltip", "unshareTooltip", - "sharedLabel", "unsharedLabel", "unshareLabel", - "portraitLabel", - "unshareConfirmLabel", "unshareConfirmAccessKey", - "unshareCancelLabel", "unshareCancelAccessKey"]) { - if (typeof data.messages[sub] != "string" || data.messages[sub].length == 0) { - return reportError('messages["' + sub + '"] is not a valid string'); - } - promptMessages[sub] = data.messages[sub]; - } - this.promptImages = promptImages; - this.promptMessages = promptMessages; - return true; + this.updateShareState(); }, get shareButton() { return document.getElementById("share-button"); }, get unsharePopup() { return document.getElementById("unsharePopup"); }, @@ -554,17 +497,17 @@ let SocialShareButton = { canSharePage: function SSB_canSharePage(aURI) { // We only allow sharing of http or https return aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')); }, updateButtonHiddenState: function SSB_updateButtonHiddenState() { let shareButton = this.shareButton; if (shareButton) - shareButton.hidden = !Social.uiVisible || this.promptImages == null || + shareButton.hidden = !Social.uiVisible || Social.provider.recommendInfo == null || !SocialUI.haveLoggedInUser() || !this.canSharePage(gBrowser.currentURI); }, onClick: function SSB_onClick(aEvent) { if (aEvent.button != 0) return; @@ -578,26 +521,27 @@ let SocialShareButton = { function updateElement(id, attrs) { let el = document.getElementById(id); Object.keys(attrs).forEach(function(attr) { el.setAttribute(attr, attrs[attr]); }); } let continueSharingButton = document.getElementById("unsharePopupContinueSharingButton"); continueSharingButton.focus(); + let recommendInfo = Social.provider.recommendInfo; updateElement("unsharePopupContinueSharingButton", - {label: this.promptMessages.unshareCancelLabel, - accesskey: this.promptMessages.unshareCancelAccessKey}); + {label: recommendInfo.messages.unshareCancelLabel, + accesskey: recommendInfo.messages.unshareCancelAccessKey}); updateElement("unsharePopupStopSharingButton", - {label: this.promptMessages.unshareConfirmLabel, - accesskey: this.promptMessages.unshareConfirmAccessKey}); + {label: recommendInfo.messages.unshareConfirmLabel, + accesskey: recommendInfo.messages.unshareConfirmAccessKey}); updateElement("socialUserPortrait", - {"aria-label": this.promptMessages.portraitLabel}); + {"aria-label": recommendInfo.messages.portraitLabel}); updateElement("socialUserRecommendedText", - {value: this.promptMessages.unshareLabel}); + {value: recommendInfo.messages.unshareLabel}); }, sharePage: function SSB_sharePage() { this.unsharePopup.hidden = false; let uri = gBrowser.currentURI; if (!Social.isPageShared(uri)) { Social.sharePage(uri); @@ -616,43 +560,44 @@ let SocialShareButton = { updateShareState: function SSB_updateShareState() { // we might have been called due to a location change, and the new location // might change the state of "can this url be shared" this.updateButtonHiddenState(); let shareButton = this.shareButton; let currentPageShared = shareButton && !shareButton.hidden && Social.isPageShared(gBrowser.currentURI); + let recommendInfo = Social.provider ? Social.provider.recommendInfo : null; // Provide a11y-friendly notification of share. let status = document.getElementById("share-button-status"); if (status) { // XXX - this should also be capable of reflecting that the page was // unshared (ie, it needs to manage three-states: (1) nothing done, (2) // shared, (3) shared then unshared) // Note that we *do* have an appropriate string from the provider for - // this (promptMessages['unsharedLabel'] but currently lack a way of + // this (recommendInfo.messages.unsharedLabel) but currently lack a way of // tracking this state) - let statusString = currentPageShared ? - this.promptMessages['sharedLabel'] : ""; + let statusString = currentPageShared && recommendInfo ? + recommendInfo.messages.sharedLabel : ""; status.setAttribute("value", statusString); } // Update the share button, if present if (!shareButton || shareButton.hidden) return; let imageURL; if (currentPageShared) { shareButton.setAttribute("shared", "true"); - shareButton.setAttribute("tooltiptext", this.promptMessages['unshareTooltip']); - imageURL = this.promptImages["unshare"] + shareButton.setAttribute("tooltiptext", recommendInfo.messages.unshareTooltip); + imageURL = recommendInfo.images.unshare; } else { shareButton.removeAttribute("shared"); - shareButton.setAttribute("tooltiptext", this.promptMessages['shareTooltip']); - imageURL = this.promptImages["share"] + shareButton.setAttribute("tooltiptext", recommendInfo.messages.shareTooltip); + imageURL = recommendInfo.images.share; } shareButton.src = imageURL; } }; var SocialMenu = { populate: function SocialMenu_populate() { // This menu is only accessible through keyboard navigation.
--- a/browser/base/content/socialchat.xml +++ b/browser/base/content/socialchat.xml @@ -62,31 +62,16 @@ ]]></body> </method> </implementation> <handlers> <handler event="focus" phase="capturing"> this.parentNode.selectedChat = this; </handler> - <handler event="DOMContentLoaded"><![CDATA[ - this.isActive = !this.minimized; - if (this._callback) this._callback(this.iframe.contentWindow); - let chatbox = this; - function chatActivity() { - chatbox.setAttribute("activity", true); - chatbox.parentNode.updateTitlebar(chatbox); - }; - let iframeWindow = this.iframe.contentWindow; - iframeWindow.addEventListener("socialChatActivity", chatActivity); - iframeWindow.addEventListener("unload", function unload() { - iframeWindow.removeEventListener("unload", unload); - iframeWindow.removeEventListener("socialChatActivity", chatActivity); - }); - ]]></handler> <handler event="DOMTitleChanged"><![CDATA[ this.setAttribute('label', this.iframe.contentDocument.title); this.parentNode.updateTitlebar(this); ]]></handler> <handler event="DOMLinkAdded"><![CDATA[ // much of this logic is from DOMLinkHandler in browser.js // this sets the presence icon for a chat user, we simply use favicon style updating let link = event.originalTarget; @@ -274,18 +259,19 @@ this.menuitemMap.set(aChatbox, menu); this.menupopup.appendChild(menu); this.nub.collapsed = false; ]]></body> </method> <method name="showChat"> <parameter name="aChatbox"/> + <parameter name="aMode"/> <body><![CDATA[ - if (aChatbox.minimized) + if ((aMode != "minimized") && aChatbox.minimized) aChatbox.minimized = false; if (this.selectedChat != aChatbox) this.selectedChat = aChatbox; if (!aChatbox.collapsed) return; // already showing - no more to do. this._showChat(aChatbox); // showing a collapsed chat might mean another needs to be collapsed // to make room... @@ -338,41 +324,86 @@ while (this.firstChild) { this._remove(this.firstChild); } // and the nub/popup must also die. this.nub.collapsed = true; ]]></body> </method> + <method name="initChatBox"> + <!-- ideally this would be a method on the chatbox itself, but the + vagaries of XBL bindings means that in some edge cases the + chatbox methods don't exist yet when we need them to... + --> + <parameter name="aChatBox"/> + <parameter name="aProvider"/> + <parameter name="aURL"/> + <parameter name="aCallback"/> + <body><![CDATA[ + // callbacks to be called when onload fires - more might be added + // if the same chat is requested before onload has fired. Set back + // to null in DOMContentLoaded, so null means DOMContentLoaded has + // already fired and new callbacks can be made immediately. + aChatBox._callbacks = [aCallback]; + let iframeWindow = aChatBox.iframe.contentWindow; + aChatBox.addEventListener("DOMContentLoaded", function DOMContentLoaded() { + aChatBox.removeEventListener("DOMContentLoaded", DOMContentLoaded); + aChatBox.isActive = !aChatBox.minimized; + for (let callback of aChatBox._callbacks) { + if (callback) + callback(iframeWindow); + } + aChatBox._callbacks = null; + function chatActivity() { + aChatBox.setAttribute("activity", true); + aChatBox.parentNode.updateTitlebar(aChatBox); + }; + + iframeWindow.addEventListener("socialChatActivity", chatActivity); + iframeWindow.addEventListener("unload", function unload() { + iframeWindow.removeEventListener("unload", unload); + iframeWindow.removeEventListener("socialChatActivity", chatActivity); + }); + }); + + aChatBox.setAttribute("origin", aProvider.origin); + aChatBox.setAttribute("src", aURL); + ]]></body> + </method> <method name="openChat"> <parameter name="aProvider"/> <parameter name="aURL"/> <parameter name="aCallback"/> <parameter name="aMode"/> <body><![CDATA[ let cb = this.chatboxForURL.get(aURL); if (cb) { cb = cb.get(); if (cb.parentNode) { - this.showChat(cb); - if (aCallback) - aCallback(cb.iframe.contentWindow); + this.showChat(cb, aMode); + if (aCallback) { + if (cb._callbacks == null) { + // DOMContentLoaded has already fired, so callback now. + aCallback(cb.iframe.contentWindow); + } else { + // DOMContentLoaded for this chat is yet to fire... + cb._callbacks.push(aCallback); + } + } return; } this.chatboxForURL.delete(aURL); } cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox"); if (aMode == "minimized") cb.setAttribute("minimized", "true"); this.selectedChat = cb; this.insertBefore(cb, this.firstChild); - cb._callback = aCallback; - cb.setAttribute("origin", aProvider.origin); - cb.setAttribute("src", aURL); + this.initChatBox(cb, aProvider, aURL, aCallback); this.chatboxForURL.set(aURL, Cu.getWeakReference(cb)); this.resize(); ]]></body> </method> <method name="resize"> <body><![CDATA[ // Checks the current size against the collapsed state of children
--- a/browser/base/content/test/browser_social_chatwindow.js +++ b/browser/base/content/test/browser_social_chatwindow.js @@ -55,16 +55,95 @@ var tests = { ok(true, "got chatbox message"); ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result); chats.selectedChat.toggle(); break; } } port.postMessage({topic: "test-init", data: { id: 1 }}); }, + testOpenMinimized: function(next) { + // In this case the sidebar opens a chat (without specifying minimized). + // We then minimize it and have the sidebar reopen the chat (again without + // minimized). On that second call the chat should open and no longer + // be minimized. + let chats = document.getElementById("pinnedchats"); + let port = Social.provider.getWorkerPort(); + let seen_opened = false; + port.onmessage = function (e) { + let topic = e.data.topic; + switch (topic) { + case "test-init-done": + port.postMessage({topic: "test-chatbox-open"}); + break; + case "chatbox-opened": + is(e.data.result, "ok", "the sidebar says it got a chatbox"); + if (!seen_opened) { + // first time we got the opened message, so minimize the chat then + // re-request the same chat to be opened - we should get the + // message again and the chat should be restored. + ok(!chats.selectedChat.minimized, "chat not initially minimized") + chats.selectedChat.minimized = true + seen_opened = true; + port.postMessage({topic: "test-chatbox-open"}); + } else { + // This is the second time we've seen this message - there should + // be exactly 1 chat open and it should no longer be minimized. + let chats = document.getElementById("pinnedchats"); + ok(!chats.selectedChat.minimized, "chat no longer minimized") + chats.selectedChat.close(); + is(chats.selectedChat, null, "should only have been one chat open"); + port.close(); + next(); + } + } + } + port.postMessage({topic: "test-init", data: { id: 1 }}); + }, + // In this case the *worker* opens a chat (so minimized is specified). + // The worker then makes the same call again - as that second call also + // specifies "minimized" the chat should *not* be restored. + testWorkerChatWindowMinimized: function(next) { + const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html"; + let port = Social.provider.getWorkerPort(); + let seen_opened = false; + ok(port, "provider has a port"); + port.postMessage({topic: "test-init"}); + port.onmessage = function (e) { + let topic = e.data.topic; + switch (topic) { + case "got-chatbox-message": + ok(true, "got a chat window opened"); + let chats = document.getElementById("pinnedchats"); + if (!seen_opened) { + // first time we got the opened message, so minimize the chat then + // re-request the same chat to be opened - we should get the + // message again and the chat should be restored. + ok(chats.selectedChat.minimized, "chatbox from worker opened as minimized"); + seen_opened = true; + port.postMessage({topic: "test-worker-chat", data: chatUrl}); + // Sadly there is no notification we can use to know the chat was + // re-opened :( So we ask the chat window to "ping" us - by then + // the second request should have made it. + chats.selectedChat.iframe.contentWindow.wrappedJSObject.pingWorker(); + } else { + // This is the second time we've seen this message - there should + // be exactly 1 chat open and it should still be minimized. + let chats = document.getElementById("pinnedchats"); + ok(chats.selectedChat.minimized, "chat still minimized") + chats.selectedChat.close(); + is(chats.selectedChat, null, "should only have been one chat open"); + port.close(); + next(); + } + break; + } + } + port.postMessage({topic: "test-worker-chat", data: chatUrl}); + }, testManyChats: function(next) { // open enough chats to overflow the window, then check // if the menupopup is visible let port = Social.provider.getWorkerPort(); ok(port, "provider has a port"); port.postMessage({topic: "test-init"}); let width = document.documentElement.boxObject.width; let numToOpen = (width / 200) + 1; @@ -280,22 +359,64 @@ var tests = { ok(chatbar.nub.hasAttribute("activity"), "nub should also have activity"); // first is collapsed, so use openChat to get it. chatbar.openChat(Social.provider, first.getAttribute("src")); ok(!first.hasAttribute("activity"), "first chat should no longer have activity"); // The nub should lose the activity flag here too todo(!chatbar.nub.hasAttribute("activity"), "Bug 806266 - nub should no longer have activity"); // TODO: tests for bug 806266 should arrange to have 2 chats collapsed // then open them checking the nub is updated correctly. - closeAllChats(); - port.close(); - next(); + // Now we will go and change the embedded iframe in the second chat and + // ensure the activity magic still works (ie, check that the unload for + // the iframe didn't cause our event handlers to be removed.) + ok(!second.hasAttribute("activity"), "second chat should have no activity"); + let subiframe = iframe2.contentDocument.getElementById("iframe"); + subiframe.contentWindow.addEventListener("unload", function subunload() { + subiframe.contentWindow.removeEventListener("unload", subunload); + // ensure all other unload listeners have fired. + executeSoon(function() { + let evt = iframe2.contentDocument.createEvent("CustomEvent"); + evt.initCustomEvent("socialChatActivity", true, true, {}); + iframe2.contentDocument.documentElement.dispatchEvent(evt); + ok(second.hasAttribute("activity"), "second chat still has activity after unloading sub-iframe"); + closeAllChats(); + port.close(); + next(); + }) + }) + subiframe.setAttribute("src", "data:text/plain:new location for iframe"); }); }, + testOnlyOneCallback: function(next) { + let chats = document.getElementById("pinnedchats"); + let port = Social.provider.getWorkerPort(); + let numOpened = 0; + port.onmessage = function (e) { + let topic = e.data.topic; + switch (topic) { + case "test-init-done": + port.postMessage({topic: "test-chatbox-open"}); + break; + case "chatbox-opened": + numOpened += 1; + port.postMessage({topic: "ping"}); + break; + case "pong": + executeSoon(function() { + is(numOpened, 1, "only got one open message"); + chats.removeAll(); + port.close(); + next(); + }); + } + } + port.postMessage({topic: "test-init", data: { id: 1 }}); + }, + // XXX - note this must be the last test until we restore the login state // between tests... testCloseOnLogout: function(next) { const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html"; let port = Social.provider.getWorkerPort(); ok(port, "provider has a port"); port.postMessage({topic: "test-init"}); port.onmessage = function (e) {
--- a/browser/base/content/test/browser_social_shareButton.js +++ b/browser/base/content/test/browser_social_shareButton.js @@ -49,17 +49,17 @@ function testInitial(finishcb) { ok(unsharePopup, "share popup exists"); let okButton = document.getElementById("unsharePopupContinueSharingButton"); let undoButton = document.getElementById("unsharePopupStopSharingButton"); let shareStatusLabel = document.getElementById("share-button-status"); // ensure the worker initialization and handshakes are all done and we // have a profile and the worker has responsed to the recommend-prompt msg. - waitForCondition(function() Social.provider.profile && SocialShareButton.promptImages != null, function() { + waitForCondition(function() Social.provider.profile && Social.provider.recommendInfo != null, function() { is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute before share button is clicked"); // check dom values let profile = Social.provider.profile; let portrait = document.getElementById("socialUserPortrait").getAttribute("src"); is(profile.portrait, portrait, "portrait is set"); let displayName = document.getElementById("socialUserDisplayName"); is(displayName.label, profile.displayName, "display name is set"); ok(!document.getElementById("unsharePopupHeader").hidden, "user profile is visible");
--- a/browser/base/content/test/social_chat.html +++ b/browser/base/content/test/social_chat.html @@ -16,10 +16,12 @@ }, false); window.addEventListener("socialTest-CloseSelf", function(e) { window.close(); }, false); </script> </head> <body onload="pingWorker();"> <p>This is a test social chat window.</p> + <!-- an iframe here so this one page generates multiple load events --> + <iframe id="iframe" src="data:text/plain:this is an iframe"></iframe> </body> </html>
--- a/browser/base/content/test/social_worker.js +++ b/browser/base/content/test/social_worker.js @@ -8,16 +8,19 @@ onconnect = function(e) { let port = e.ports[0]; port.onmessage = function onMessage(event) { let topic = event.data.topic; switch (topic) { case "test-init": testPort = port; port.postMessage({topic: "test-init-done"}); break; + case "ping": + port.postMessage({topic: "pong"}); + break; case "test-logout": apiPort.postMessage({topic: "social.user-profile", data: {}}); break; case "sidebar-message": sidebarPort = port; if (testPort && event.data.result == "ok") testPort.postMessage({topic:"got-sidebar-message"}); break;
--- a/browser/components/privatebrowsing/test/browser/perwindow/Makefile.in +++ b/browser/components/privatebrowsing/test/browser/perwindow/Makefile.in @@ -24,15 +24,17 @@ MOCHITEST_BROWSER_FILES = \ browser_privatebrowsing_geoprompt.js \ browser_privatebrowsing_geoprompt_page.html \ browser_privatebrowsing_lastpbcontextexited.js \ browser_privatebrowsing_localStorage.js \ browser_privatebrowsing_localStorage_page1.html \ browser_privatebrowsing_localStorage_page2.html \ browser_privatebrowsing_opendir.js \ browser_privatebrowsing_openlocation.js \ + browser_privatebrowsing_openLocationLastURL.js \ browser_privatebrowsing_popupblocker.js \ browser_privatebrowsing_theming.js \ + browser_privatebrowsing_urlbarfocus.js \ browser_privatebrowsing_zoomrestore.js \ popup.html \ $(NULL) include $(topsrcdir)/config/rules.mk
copy from browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_openLocationLastURL.js copy to browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_openLocationLastURL.js --- a/browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_openLocationLastURL.js +++ b/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_openLocationLastURL.js @@ -1,63 +1,63 @@ /* 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/. */ function test() { + waitForExplicitFinish(); const URL_1 = "mozilla.org"; const URL_2 = "mozilla.com"; - let openLocationLastURL = getLocationModule(); - let privateBrowsingService = - Cc["@mozilla.org/privatebrowsing;1"]. - getService(Ci.nsIPrivateBrowsingService); - - function clearHistory() { - Services.obs.notifyObservers(null, "browser:purge-session-history", ""); - } - function testURL(aTestNumber, aValue) { - is(openLocationLastURL.value, aValue, + function testURL(aOpenLocation, aTestNumber, aValue) { + is(aOpenLocation.value, aValue, "Test: " + aTestNumber + ": Validate last url value."); } - // Clean to start testing. - is(typeof openLocationLastURL, "object", "Validate type of last url."); - openLocationLastURL.reset(); - testURL(1, ""); + whenNewWindowLoaded({private: false}, function(normalWindow) { + whenNewWindowLoaded({private: true}, function(privateWindow) { + let openLocationLastURL = getLocationModule(normalWindow); + let openLocationLastURLPB = getLocationModule(privateWindow); - // Test without private browsing. - openLocationLastURL.value = URL_1; - testURL(2, URL_1); - openLocationLastURL.value = ""; - testURL(3, ""); - openLocationLastURL.value = URL_2; - testURL(4, URL_2); - clearHistory(); - testURL(5, ""); + // Clean to start testing. + is(typeof openLocationLastURL, "object", "Validate Normal type of last url."); + is(typeof openLocationLastURLPB, "object", "Validate PB type of last url."); + openLocationLastURL.reset(); + openLocationLastURLPB.reset(); + testURL(openLocationLastURL, 1, ""); - // Test changing private browsing. - openLocationLastURL.value = URL_2; - privateBrowsingService.privateBrowsingEnabled = true; - testURL(6, ""); - privateBrowsingService.privateBrowsingEnabled = false; - testURL(7, URL_2); - privateBrowsingService.privateBrowsingEnabled = true; - openLocationLastURL.value = URL_1; - testURL(8, URL_1); - privateBrowsingService.privateBrowsingEnabled = false; - testURL(9, URL_2); - privateBrowsingService.privateBrowsingEnabled = true; - openLocationLastURL.value = URL_1; - testURL(10, URL_1); + // Test without private browsing. + openLocationLastURL.value = URL_1; + testURL(openLocationLastURL, 2, URL_1); + openLocationLastURL.value = ""; + testURL(openLocationLastURL, 3, ""); + openLocationLastURL.value = URL_2; + testURL(openLocationLastURL, 4, URL_2); + clearHistory(); + testURL(openLocationLastURL, 5, ""); - // Test cleaning history. - clearHistory(); - testURL(11, ""); - privateBrowsingService.privateBrowsingEnabled = false; - testURL(12, ""); + // Test changing private browsing. + openLocationLastURL.value = URL_2; + testURL(openLocationLastURLPB, 6, ""); + testURL(openLocationLastURL, 7, URL_2); + openLocationLastURLPB.value = URL_1; + testURL(openLocationLastURLPB, 8, URL_1); + testURL(openLocationLastURL, 9, URL_2); + openLocationLastURLPB.value = URL_1; + testURL(openLocationLastURLPB, 10, URL_1); + + // Test cleaning history. + clearHistory(); + testURL(openLocationLastURLPB, 11, ""); + testURL(openLocationLastURL, 12, ""); + + privateWindow.close(); + normalWindow.close(); + finish(); + }); + }); } -function getLocationModule() { +function getLocationModule(aWindow) { let openLocationModule = {}; Cu.import("resource:///modules/openLocationLastURL.jsm", openLocationModule); - return new openLocationModule.OpenLocationLastURL(window); -} + return new openLocationModule.OpenLocationLastURL(aWindow); +} \ No newline at end of file
copy from browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_urlbarfocus.js copy to browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_urlbarfocus.js --- a/browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_urlbarfocus.js +++ b/browser/components/privatebrowsing/test/browser/perwindow/browser_privatebrowsing_urlbarfocus.js @@ -1,62 +1,67 @@ /* 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/. */ -// This test makes sure that the URL bar is focused when entering the private browsing mode. +// This test makes sure that the URL bar is focused when entering the private window. function test() { - // initialization - let pb = Cc["@mozilla.org/privatebrowsing;1"]. - getService(Ci.nsIPrivateBrowsingService); + waitForExplicitFinish(); const TEST_URL = "data:text/plain,test"; - gBrowser.selectedTab = gBrowser.addTab(); - let browser = gBrowser.selectedBrowser; - browser.addEventListener("load", function() { - browser.removeEventListener("load", arguments.callee, true); - - // ensure that the URL bar is not focused initially - browser.focus(); - isnot(document.commandDispatcher.focusedElement, gURLBar.inputField, - "URL Bar should not be focused before entering the private browsing mode"); - // ensure that the URL bar is not empty initially - isnot(gURLBar.value, "", "URL Bar should no longer be empty after leaving the private browsing mode"); - // enter private browsing mode - pb.privateBrowsingEnabled = true; - browser = gBrowser.selectedBrowser; - browser.addEventListener("load", function() { - browser.removeEventListener("load", arguments.callee, true); + function checkUrlbarFocus(aWin, aIsPrivate, aCallback) { + let urlbar = aWin.gURLBar; + if (aIsPrivate) { + is(aWin.document.commandDispatcher.focusedElement, urlbar.inputField, + "URL Bar should be focused inside the private window"); + is(urlbar.value, "", + "URL Bar should be empty inside the private window"); + } else { + isnot(aWin.document.commandDispatcher.focusedElement, urlbar.inputField, + "URL Bar should not be focused after opening window"); + isnot(urlbar.value, "", + "URL Bar should not be empty after opening window"); + } + aCallback(); + } - // setTimeout is needed here because the onload handler of about:privatebrowsing sets the focus - setTimeout(function() { - // ensure that the URL bar is focused inside the private browsing mode - is(document.commandDispatcher.focusedElement, gURLBar.inputField, - "URL Bar should be focused inside the private browsing mode"); + let windowsToClose = []; + function testOnWindow(aPrivate, aCallback) { + whenNewWindowLoaded({private: aPrivate}, function(win) { + windowsToClose.push(win); + executeSoon(function() aCallback(win)); + }); + } - // ensure that the URL bar is emptied inside the private browsing mode - is(gURLBar.value, "", "URL Bar should be empty inside the private browsing mode"); - - // leave private browsing mode - pb.privateBrowsingEnabled = false; - browser = gBrowser.selectedBrowser; - browser.addEventListener("load", function() { - browser.removeEventListener("load", arguments.callee, true); - - // ensure that the URL bar is no longer focused after leaving the private browsing mode - isnot(document.commandDispatcher.focusedElement, gURLBar.inputField, - "URL Bar should no longer be focused after leaving the private browsing mode"); + registerCleanupFunction(function() { + windowsToClose.forEach(function(win) { + win.close(); + }); + }); - // ensure that the URL bar is no longer empty after leaving the private browsing mode - isnot(gURLBar.value, "", "URL Bar should no longer be empty after leaving the private browsing mode"); + function whenLoadTab(aPrivate, aCallback) { + testOnWindow(aPrivate, function(win) { + let browser = win.gBrowser.selectedBrowser; + browser.addEventListener("load", function() { + browser.removeEventListener("load", arguments.callee, true); + aCallback(win); + }, true); + if (!aPrivate) { + browser.focus(); + browser.loadURI(TEST_URL); + } + }); + } - gBrowser.removeCurrentTab(); - finish(); - }, true); - }, 0); - }, true); - }, true); - content.location = TEST_URL; - - waitForExplicitFinish(); + whenLoadTab(false, function(win) { + checkUrlbarFocus(win, false, function() { + whenLoadTab(true, function(win) { + checkUrlbarFocus(win, true, function() { + whenLoadTab(false, function(win) { + checkUrlbarFocus(win, false, finish); + }); + }); + }); + }); + }); }
--- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -14,17 +14,16 @@ #include <float.h> #endif #if defined(SOLARIS) #include <ieeefp.h> #endif #include "nsAString.h" -#include "nsIStatefulFrame.h" #include "nsNodeInfoManager.h" #include "nsIXPCScriptable.h" #include "nsDataHashtable.h" #include "nsIDOMEvent.h" #include "nsTArray.h" #include "nsReadableUtils.h" #include "nsINode.h" #include "nsIDOMNode.h" @@ -477,17 +476,16 @@ public: } // Returns the subject principal. Guaranteed to return non-null. May only // be called when nsContentUtils is initialized. static nsIPrincipal* GetSubjectPrincipal(); static nsresult GenerateStateKey(nsIContent* aContent, const nsIDocument* aDocument, - nsIStatefulFrame::SpecialStateID aID, nsACString& aKey); /** * Create a new nsIURI from aSpec, using aBaseURI as the base. The * origin charset of the new nsIURI will be the document charset of * aDocument. */ static nsresult NewURIWithDocumentCharset(nsIURI** aResult,
--- a/content/base/src/FileIOObject.cpp +++ b/content/base/src/FileIOObject.cpp @@ -28,26 +28,26 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_ NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_CLASS(FileIOObject) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileIOObject, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mProgressNotifier) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mError) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) // Can't traverse mChannel because it's a multithreaded object. NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileIOObject, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mProgressNotifier) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mError) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_EVENT_HANDLER(FileIOObject, abort); NS_IMPL_EVENT_HANDLER(FileIOObject, error); NS_IMPL_EVENT_HANDLER(FileIOObject, progress); FileIOObject::FileIOObject() : mProgressEventWasDelayed(false),
--- a/content/base/src/WebSocket.cpp +++ b/content/base/src/WebSocket.cpp @@ -603,27 +603,27 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_B NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPrincipal) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mURI) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, nsDOMEventTargetHelper) tmp->Disconnect(); - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPrincipal) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mURI) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIRequest)
--- a/content/base/src/nsContentIterator.cpp +++ b/content/base/src/nsContentIterator.cpp @@ -1143,20 +1143,20 @@ protected: NS_IMPL_ADDREF_INHERITED(nsContentSubtreeIterator, nsContentIterator) NS_IMPL_RELEASE_INHERITED(nsContentSubtreeIterator, nsContentIterator) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator) NS_INTERFACE_MAP_END_INHERITING(nsContentIterator) NS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSubtreeIterator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsContentSubtreeIterator, nsContentIterator) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRange, nsIDOMRange) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRange) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsContentSubtreeIterator, nsContentIterator) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRange) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRange) NS_IMPL_CYCLE_COLLECTION_UNLINK_END /****************************************************** * repository cruft ******************************************************/
--- a/content/base/src/nsContentList.cpp +++ b/content/base/src/nsContentList.cpp @@ -39,26 +39,26 @@ using namespace mozilla::dom; nsBaseContentList::~nsBaseContentList() { } NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mElements) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack() && MOZ_LIKELY(!cb.WantAllTraces())) { return NS_SUCCESS_INTERRUPTED_TRAVERSE; } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(mElements) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsBaseContentList) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList) if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) { @@ -141,21 +141,21 @@ int32_t nsBaseContentList::IndexOf(nsIContent* aContent) { return IndexOf(aContent, true); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsSimpleContentList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsSimpleContentList, nsBaseContentList) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsSimpleContentList, nsBaseContentList) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList) NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList) NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList) NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
--- a/content/base/src/nsContentSink.cpp +++ b/content/base/src/nsContentSink.cpp @@ -67,27 +67,26 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentObserver) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSink) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsContentSink) if (tmp->mDocument) { tmp->mDocument->RemoveObserver(tmp); } - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParser) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNodeInfoManager) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptLoader) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParser) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNodeInfoManager, - nsNodeInfoManager) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptLoader) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END nsContentSink::nsContentSink() { // We have a zeroing operator new NS_ASSERTION(!mLayoutStarted, "What?"); NS_ASSERTION(!mDynamicLowerValue, "What?");
--- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -2167,49 +2167,37 @@ static inline bool IsAutocompleteOff(con { return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocomplete, NS_LITERAL_STRING("off"), eIgnoreCase); } /*static*/ nsresult nsContentUtils::GenerateStateKey(nsIContent* aContent, const nsIDocument* aDocument, - nsIStatefulFrame::SpecialStateID aID, nsACString& aKey) { aKey.Truncate(); uint32_t partID = aDocument ? aDocument->GetPartID() : 0; - // SpecialStateID case - e.g. scrollbars around the content window - // The key in this case is a special state id - if (nsIStatefulFrame::eNoID != aID) { - KeyAppendInt(partID, aKey); // first append a partID - KeyAppendInt(aID, aKey); - return NS_OK; - } - // We must have content if we're not using a special state id NS_ENSURE_TRUE(aContent, NS_ERROR_FAILURE); // Don't capture state for anonymous content if (aContent->IsInAnonymousSubtree()) { return NS_OK; } if (IsAutocompleteOff(aContent)) { return NS_OK; } nsCOMPtr<nsIHTMLDocument> htmlDocument(do_QueryInterface(aContent->GetCurrentDoc())); KeyAppendInt(partID, aKey); // first append a partID - // Make sure we can't possibly collide with an nsIStatefulFrame - // special id of some sort - KeyAppendInt(nsIStatefulFrame::eNoID, aKey); bool generatedUniqueKey = false; if (htmlDocument) { // Flush our content model so it'll be up to date // If this becomes unnecessary and the following line is removed, // please also remove the corresponding flush operation from // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.) aContent->GetCurrentDoc()->FlushPendingNotifications(Flush_Content); @@ -3954,19 +3942,18 @@ nsContentUtils::TraverseListenerManager( return; } EventListenerManagerMapEntry *entry = static_cast<EventListenerManagerMapEntry *> (PL_DHashTableOperate(&sEventListenerManagersHash, aNode, PL_DHASH_LOOKUP)); if (PL_DHASH_ENTRY_IS_BUSY(entry)) { - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR(entry->mListenerManager, - nsEventListenerManager, - "[via hash] mListenerManager") + CycleCollectionNoteChild(cb, entry->mListenerManager.get(), + "[via hash] mListenerManager"); } } nsEventListenerManager* nsContentUtils::GetListenerManager(nsINode *aNode, bool aCreateIfNotFound) { if (!aCreateIfNotFound && !aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
--- a/content/base/src/nsDOMFile.cpp +++ b/content/base/src/nsDOMFile.cpp @@ -683,17 +683,17 @@ nsDOMMemoryFile::GetInternalStream(nsIIn nsDOMMemoryFile::DataOwner::sDataOwners; /* static */ bool nsDOMMemoryFile::DataOwner::sMemoryReporterRegistered; NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(DOMMemoryFileDataOwnerSizeOf, "memory-file-data"); -class nsDOMMemoryFileDataOwnerMemoryReporter +class nsDOMMemoryFileDataOwnerMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter { NS_DECL_ISUPPORTS NS_IMETHOD GetName(nsACString& aName) { aName.AssignASCII("dom-memory-file-data-owner"); return NS_OK;
--- a/content/base/src/nsDOMFileReader.cpp +++ b/content/base/src/nsDOMFileReader.cpp @@ -56,25 +56,25 @@ using namespace mozilla; using mozilla::dom::EncodingUtils; using mozilla::dom::FileIOObject; NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMFileReader) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMFileReader, FileIOObject) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFile) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFile) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMFileReader, FileIOObject) tmp->mResultArrayBuffer = nullptr; - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFile) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFile) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsDOMFileReader, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) NS_IMPL_CYCLE_COLLECTION_TRACE_END
--- a/content/base/src/nsDOMMutationObserver.cpp +++ b/content/base/src/nsDOMMutationObserver.cpp @@ -38,27 +38,27 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsIDOMMutationRecord) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MutationRecord) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationRecord) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationRecord) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationRecord) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTarget) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPreviousSibling) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNextSibling) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTarget) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreviousSibling) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextSibling) tmp->mAddedNodes = nullptr; tmp->mRemovedNodes = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationRecord) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTarget) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPreviousSibling) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNextSibling) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreviousSibling) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextSibling) NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAddedNodes"); cb.NoteXPCOMChild(static_cast<nsIDOMNodeList*>(tmp->mAddedNodes)); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRemovedNodes"); cb.NoteXPCOMChild(static_cast<nsIDOMNodeList*>(tmp->mRemovedNodes)); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMETHODIMP nsDOMMutationRecord::GetType(nsAString& aType) @@ -401,34 +401,34 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMut NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptContext) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) for (int32_t i = 0; i < tmp->mReceivers.Count(); ++i) { tmp->mReceivers[i]->Disconnect(false); } tmp->mReceivers.Clear(); - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mPendingMutations) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCallback) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingMutations) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) // No need to handle mTransientReceivers NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptContext) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOwner) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mReceivers) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPendingMutations) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCallback) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceivers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingMutations) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) // No need to handle mTransientReceivers NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END nsMutationReceiver* nsDOMMutationObserver::GetReceiverFor(nsINode* aNode, bool aMayCreate) { if (!aMayCreate && !aNode->MayHaveDOMMutationObserver()) { return nullptr;
--- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1634,52 +1634,50 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ // Traverse the mChildren nsAttrAndChildArray. for (int32_t indx = int32_t(tmp->mChildren.ChildCount()); indx > 0; --indx) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildren[i]"); cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1)); } // Traverse all nsIDocument pointer members. - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSecurityInfo) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDisplayDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument) // Traverse all nsDocument nsCOMPtrs. - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParser) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptGlobalObject) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mListenerManager, - nsEventListenerManager) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDOMStyleSheets) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptLoader) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) tmp->mRadioGroups.EnumerateRead(RadioGroupsTraverser, &cb); // The boxobject for an element will only exist as long as it's in the // document, so we'll traverse the table here instead of from the element. if (tmp->mBoxObjectTable) { tmp->mBoxObjectTable->EnumerateRead(BoxObjectTraverser, &cb); } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mStyleAttrStyleSheet, nsIStyleSheet) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXPathEvaluatorTearoff) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLayoutHistoryState) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnloadBlocker) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBaseNodeWithHref) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDOMImplementation) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mImageMaps, - nsIDOMNodeList) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOriginalDocument) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCachedEncoder) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStateObjectCached) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleAttrStyleSheet) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXPathEvaluatorTearoff) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLayoutHistoryState) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstBaseNodeWithHref) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached) // Traverse all our nsCOMArrays. - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mStyleSheets) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCatalogSheets) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPreloadingImages) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCatalogSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]"); cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i]); } // Traverse animation components if (tmp->mAnimationController) { @@ -1714,28 +1712,28 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns // Unlink the mChildren nsAttrAndChildArray. for (int32_t indx = int32_t(tmp->mChildren.ChildCount()) - 1; indx >= 0; --indx) { tmp->mChildren.ChildAt(indx)->UnbindFromTree(); tmp->mChildren.RemoveChildAt(indx); } tmp->mFirstChild = nullptr; - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXPathEvaluatorTearoff) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mXPathEvaluatorTearoff) tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDisplayDocument) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstBaseNodeWithHref) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDOMImplementation) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mImageMaps) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOriginalDocument) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCachedEncoder) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFirstBaseNodeWithHref) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder) tmp->mParentDocument = nullptr; - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mPreloadingImages) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) if (tmp->mBoxObjectTable) { tmp->mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr); delete tmp->mBoxObjectTable; tmp->mBoxObjectTable = nullptr; }
--- a/content/base/src/nsDocumentEncoder.cpp +++ b/content/base/src/nsDocumentEncoder.cpp @@ -170,29 +170,29 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocume NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder) NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocumentEncoder) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSelection) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRange) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNode) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCommonParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRange) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNode) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommonParent) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocumentEncoder) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSelection) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRange, nsIDOMRange) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNode) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCommonParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRange) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommonParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nullptr) { Initialize(); mMimeType.AssignLiteral("text/plain"); }
--- a/content/base/src/nsEventSource.cpp +++ b/content/base/src/nsEventSource.cpp @@ -99,23 +99,23 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_B NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsEventSource, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsEventSource, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSrc) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNotificationCallbacks) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLoadGroup) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannelEventSink) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mHttpChannel) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTimer) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mUnicodeDecoder) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadGroup) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHttpChannel) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnicodeDecoder) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsEventSource, nsDOMEventTargetHelper) tmp->Close(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END DOMCI_DATA(EventSource, nsEventSource) @@ -640,21 +640,21 @@ public: private: nsRefPtr<nsEventSource> mEventSource; }; NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackFwr) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncVerifyRedirectCallbackFwr) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mEventSource, nsIEventSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventSource) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncVerifyRedirectCallbackFwr) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventSource) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackFwr) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackFwr)
--- a/content/base/src/nsFrameLoader.cpp +++ b/content/base/src/nsFrameLoader.cpp @@ -254,26 +254,26 @@ nsContentView::GetId(nsContentViewId* aI // small (but not 1) branching factor. With large branching factors the number // of shells can rapidly become huge and run us out of memory. To solve that, // we'd need to re-institute a fixed version of bug 98158. #define MAX_DEPTH_CONTENT_FRAMES 10 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameLoader) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameLoader) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocShell) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMessageManager) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChildMessageManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildMessageManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameLoader) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocShell) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "nsFrameLoader::mMessageManager"); cb.NoteXPCOMChild(static_cast<nsIContentFrameMessageManager*>(tmp->mMessageManager.get())); - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChildMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader) NS_INTERFACE_MAP_ENTRY(nsIFrameLoader) NS_INTERFACE_MAP_ENTRY(nsIContentViewManager)
--- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -59,26 +59,26 @@ IsChromeProcess() NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager) uint32_t count = tmp->mListeners.Length(); for (uint32_t i = 0; i < count; i++) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mListeners[i] mListener"); cb.NoteXPCOMChild(tmp->mListeners[i].mListener.get()); } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mChildManagers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager) tmp->mListeners.Clear(); for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) { static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])-> Disconnect(false); } - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mChildManagers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentFrameMessageManager) /* nsFrameMessageManager implements nsIMessageSender and nsIMessageBroadcaster, * both of which descend from nsIMessageListenerManager. QI'ing to @@ -1006,17 +1006,17 @@ nsFrameScriptExecutor::InitTabChildGloba return true; } // static void nsFrameScriptExecutor::Traverse(nsFrameScriptExecutor *tmp, nsCycleCollectionTraversalCallback &cb) { - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) nsIXPConnect* xpc = nsContentUtils::XPConnect(); if (xpc) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCx"); xpc->NoteJSContext(tmp->mCx, cb); } } NS_IMPL_ISUPPORTS1(nsScriptCacheCleaner, nsIObserver)
--- a/content/base/src/nsINode.cpp +++ b/content/base/src/nsINode.cpp @@ -1167,17 +1167,17 @@ nsINode::Traverse(nsINode *tmp, nsCycleC if (parent && !parent->UnoptimizableCCNode() && parent->IsBlack()) { NS_ABORT_IF_FALSE(parent->IndexOf(tmp) >= 0, "Parent doesn't own us?"); return false; } } } } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNodeInfo) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfo) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetParent()) nsSlots *slots = tmp->GetExistingSlots(); if (slots) { slots->Traverse(cb); } if (tmp->HasProperties()) {
--- a/content/base/src/nsInProcessTabChildGlobal.cpp +++ b/content/base/src/nsInProcessTabChildGlobal.cpp @@ -146,23 +146,23 @@ nsInProcessTabChildGlobal::Init() return NS_OK; } NS_IMPL_CYCLE_COLLECTION_CLASS(nsInProcessTabChildGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsInProcessTabChildGlobal, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMessageManager) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mGlobal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsInProcessTabChildGlobal, nsDOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager) nsFrameScriptExecutor::Traverse(tmp, cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsInProcessTabChildGlobal) NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager) NS_INTERFACE_MAP_ENTRY(nsIMessageSender) NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender) NS_INTERFACE_MAP_ENTRY(nsIContentFrameMessageManager)
--- a/content/base/src/nsNodeInfo.cpp +++ b/content/base/src/nsNodeInfo.cpp @@ -178,18 +178,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ } cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); } else { NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsNodeInfo, tmp->mRefCnt.get()) } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mOwnerManager, - nsNodeInfoManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mOwnerManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeInfo) NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(nsNodeInfo, LastRelease()) NS_INTERFACE_TABLE_HEAD(nsNodeInfo) NS_INTERFACE_TABLE1(nsNodeInfo, nsINodeInfo) NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsNodeInfo) NS_INTERFACE_MAP_END
--- a/content/base/src/nsNodeIterator.cpp +++ b/content/base/src/nsNodeIterator.cpp @@ -155,22 +155,22 @@ nsNodeIterator::~nsNodeIterator() /* * nsISupports and cycle collection stuff */ NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeIterator) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNodeIterator) if (!tmp->mDetached && tmp->mRoot) tmp->mRoot->RemoveMutationObserver(tmp); - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFilter) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilter) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeIterator) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFilter) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilter) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END DOMCI_DATA(NodeIterator, nsNodeIterator) // QueryInterface implementation for nsNodeIterator NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeIterator) NS_INTERFACE_MAP_ENTRY(nsIDOMNodeIterator) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
--- a/content/base/src/nsRange.cpp +++ b/content/base/src/nsRange.cpp @@ -268,19 +268,19 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Range) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsRange) tmp->Reset(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsRange) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStartParent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEndParent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END static void RangeHashTableDtor(void* aObject, nsIAtom* aPropertyName, void* aPropertyValue, void* aData) { nsRange::RangeHashTable* ranges = static_cast<nsRange::RangeHashTable*>(aPropertyValue);
--- a/content/base/src/nsStyleLinkElement.cpp +++ b/content/base/src/nsStyleLinkElement.cpp @@ -43,17 +43,17 @@ nsStyleLinkElement::Unlink() { mStyleSheet = nullptr; } void nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback &cb) { nsStyleLinkElement* tmp = this; - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStyleSheet); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet); } NS_IMETHODIMP nsStyleLinkElement::SetStyleSheet(nsIStyleSheet* aStyleSheet) { nsRefPtr<nsCSSStyleSheet> cssSheet = do_QueryObject(mStyleSheet); if (cssSheet) { cssSheet->SetOwningNode(nullptr);
--- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -591,47 +591,46 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXMLHttpRequest) return tmp->IsBlack(); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLHttpRequest, nsXHREventTarget) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mReadRequest) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mResponseXML) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCORSPreflightChannel) - - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXMLParserStreamListener) - - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannelEventSink) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mProgressEventSink) - - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mUpload, - nsIXMLHttpRequestUpload) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadRequest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCORSPreflightChannel) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXMLHttpRequest, nsXHREventTarget) tmp->mResultArrayBuffer = nullptr; tmp->mResultJSON = JSVAL_VOID; - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContext) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mReadRequest) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mResponseXML) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCORSPreflightChannel) - - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXMLParserStreamListener) - - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannelEventSink) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mProgressEventSink) - - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUpload) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadRequest) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCORSPreflightChannel) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsXMLHttpRequest, nsXHREventTarget) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResultJSON) NS_IMPL_CYCLE_COLLECTION_TRACE_END @@ -3560,21 +3559,21 @@ public: private: nsRefPtr<nsXMLHttpRequest> mXHR; }; NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncVerifyRedirectCallbackForwarder) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mXHR, nsIXMLHttpRequest) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncVerifyRedirectCallbackForwarder) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXHR) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackForwarder) @@ -4080,23 +4079,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsIDOMLSProgressEvent) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XMLHttpProgressEvent) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpProgressEvent) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpProgressEvent) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpProgressEvent) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mInner); - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpProgressEvent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mInner) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindow); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMETHODIMP nsXMLHttpProgressEvent::GetInput(nsIDOMLSInput * *aInput) { *aInput = nullptr; return NS_ERROR_NOT_IMPLEMENTED; } @@ -4147,21 +4146,21 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHt // Can't NS_IMPL_CYCLE_COLLECTION_1 because mXHR has ambiguous // inheritance from nsISupports. NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier) if (tmp->mXHR) { tmp->mXHR->mXPCOMifier = nullptr; } -NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXHR) +NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mXHR, nsIXMLHttpRequest) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMETHODIMP nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID & aIID, void **aResult) { // Return ourselves for the things we implement (except // nsIInterfaceRequestor) and the XHR for the rest. if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
--- a/content/canvas/src/CanvasRenderingContext2D.cpp +++ b/content/canvas/src/CanvasRenderingContext2D.cpp @@ -1,3937 +1,3926 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * 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 "base/basictypes.h" -#include "CanvasRenderingContext2D.h" - -#include "nsIDOMXULElement.h" - -#include "prenv.h" - -#include "nsIServiceManager.h" -#include "nsMathUtils.h" - -#include "nsContentUtils.h" - -#include "nsIDocument.h" -#include "nsHTMLCanvasElement.h" -#include "nsSVGEffects.h" -#include "nsPresContext.h" -#include "nsIPresShell.h" -#include "nsIVariant.h" - -#include "nsIInterfaceRequestorUtils.h" -#include "nsIFrame.h" -#include "nsError.h" -#include "nsIScriptError.h" - -#include "nsCSSParser.h" -#include "mozilla/css/StyleRule.h" -#include "mozilla/css/Declaration.h" -#include "nsComputedDOMStyle.h" -#include "nsStyleSet.h" - -#include "nsPrintfCString.h" - -#include "nsReadableUtils.h" - -#include "nsColor.h" -#include "nsGfxCIID.h" -#include "nsIScriptSecurityManager.h" -#include "nsIDocShell.h" -#include "nsIDOMWindow.h" -#include "nsPIDOMWindow.h" -#include "nsIDocShellTreeItem.h" -#include "nsIDocShellTreeNode.h" -#include "nsIXPConnect.h" -#include "nsDisplayList.h" - -#include "nsTArray.h" - -#include "imgIEncoder.h" - -#include "gfxContext.h" -#include "gfxASurface.h" -#include "gfxImageSurface.h" -#include "gfxPlatform.h" -#include "gfxFont.h" -#include "gfxBlur.h" -#include "gfxUtils.h" -#include "gfxFontMissingGlyphs.h" - -#include "nsFrameManager.h" -#include "nsFrameLoader.h" -#include "nsBidi.h" -#include "nsBidiPresUtils.h" -#include "Layers.h" -#include "CanvasUtils.h" -#include "nsIMemoryReporter.h" -#include "nsStyleUtil.h" -#include "CanvasImageCache.h" - -#include <algorithm> - -#include "jsapi.h" -#include "jsfriendapi.h" - -#include "mozilla/Assertions.h" -#include "mozilla/CheckedInt.h" -#include "mozilla/dom/ContentParent.h" -#include "mozilla/dom/ImageData.h" -#include "mozilla/dom/PBrowserParent.h" -#include "mozilla/dom/TypedArray.h" -#include "mozilla/gfx/2D.h" -#include "mozilla/gfx/PathHelpers.h" -#include "mozilla/ipc/DocumentRendererParent.h" -#include "mozilla/ipc/PDocumentRendererParent.h" -#include "mozilla/Preferences.h" -#include "mozilla/Telemetry.h" -#include "mozilla/unused.h" -#include "nsCCUncollectableMarker.h" -#include "nsWrapperCacheInlines.h" -#include "nsJSUtils.h" -#include "XPCQuickStubs.h" -#include "mozilla/dom/BindingUtils.h" -#include "nsHTMLImageElement.h" -#include "nsHTMLVideoElement.h" -#include "mozilla/dom/CanvasRenderingContext2DBinding.h" - -#ifdef XP_WIN -#include "gfxWindowsPlatform.h" -#endif - -// windows.h (included by chromium code) defines this, in its infinite wisdom -#undef DrawText - -using namespace mozilla; -using namespace mozilla::CanvasUtils; -using namespace mozilla::css; -using namespace mozilla::gfx; -using namespace mozilla::ipc; -using namespace mozilla::layers; - -namespace mgfx = mozilla::gfx; - -#define NS_TEXTMETRICSAZURE_PRIVATE_IID \ - {0x9793f9e7, 0x9dc1, 0x4e9c, {0x81, 0xc8, 0xfc, 0xa7, 0x14, 0xf4, 0x30, 0x79}} - -namespace mozilla { -namespace dom { - -static float kDefaultFontSize = 10.0; -static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); -static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); - -// Cap sigma to avoid overly large temp surfaces. -const Float SIGMA_MAX = 100; - -/* Memory reporter stuff */ -static nsIMemoryReporter *gCanvasAzureMemoryReporter = nullptr; -static int64_t gCanvasAzureMemoryUsed = 0; - -static int64_t GetCanvasAzureMemoryUsed() { - return gCanvasAzureMemoryUsed; -} - -// This is KIND_OTHER because it's not always clear where in memory the pixels -// of a canvas are stored. Furthermore, this memory will be tracked by the -// underlying surface implementations. See bug 655638 for details. -NS_MEMORY_REPORTER_IMPLEMENT(CanvasAzureMemory, - "canvas-2d-pixel-bytes", - KIND_OTHER, - UNITS_BYTES, - GetCanvasAzureMemoryUsed, - "Memory used by 2D canvases. Each canvas requires (width * height * 4) " - "bytes.") - -class CanvasRadialGradient : public CanvasGradient -{ -public: - CanvasRadialGradient(const Point &aBeginOrigin, Float aBeginRadius, - const Point &aEndOrigin, Float aEndRadius) - : CanvasGradient(RADIAL) - , mCenter1(aBeginOrigin) - , mCenter2(aEndOrigin) - , mRadius1(aBeginRadius) - , mRadius2(aEndRadius) - { - } - - Point mCenter1; - Point mCenter2; - Float mRadius1; - Float mRadius2; -}; - -class CanvasLinearGradient : public CanvasGradient -{ -public: - CanvasLinearGradient(const Point &aBegin, const Point &aEnd) - : CanvasGradient(LINEAR) - , mBegin(aBegin) - , mEnd(aEnd) - { - } - -protected: - friend class CanvasGeneralPattern; - - // Beginning of linear gradient. - Point mBegin; - // End of linear gradient. - Point mEnd; -}; - -// This class is named 'GeneralCanvasPattern' instead of just -// 'GeneralPattern' to keep Windows PGO builds from confusing the -// GeneralPattern class in gfxContext.cpp with this one. - -class CanvasGeneralPattern -{ -public: - typedef CanvasRenderingContext2D::Style Style; - typedef CanvasRenderingContext2D::ContextState ContextState; - - CanvasGeneralPattern() : mPattern(nullptr) {} - ~CanvasGeneralPattern() - { - if (mPattern) { - mPattern->~Pattern(); - } - } - - Pattern& ForStyle(CanvasRenderingContext2D *aCtx, - Style aStyle, - DrawTarget *aRT) - { - // This should only be called once or the mPattern destructor will - // not be executed. - NS_ASSERTION(!mPattern, "ForStyle() should only be called once on CanvasGeneralPattern!"); - - const ContextState &state = aCtx->CurrentState(); - - if (state.StyleIsColor(aStyle)) { - mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); - } else if (state.gradientStyles[aStyle] && - state.gradientStyles[aStyle]->GetType() == CanvasGradient::LINEAR) { - CanvasLinearGradient *gradient = - static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get()); - - mPattern = new (mLinearGradientPattern.addr()) - LinearGradientPattern(gradient->mBegin, gradient->mEnd, - gradient->GetGradientStopsForTarget(aRT)); - } else if (state.gradientStyles[aStyle] && - state.gradientStyles[aStyle]->GetType() == CanvasGradient::RADIAL) { - CanvasRadialGradient *gradient = - static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get()); - - mPattern = new (mRadialGradientPattern.addr()) - RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, - gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT)); - } else if (state.patternStyles[aStyle]) { - if (aCtx->mCanvasElement) { - CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement, - state.patternStyles[aStyle]->mPrincipal, - state.patternStyles[aStyle]->mForceWriteOnly, - state.patternStyles[aStyle]->mCORSUsed); - } - - ExtendMode mode; - if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::NOREPEAT) { - mode = EXTEND_CLAMP; - } else { - mode = EXTEND_REPEAT; - } - mPattern = new (mSurfacePattern.addr()) - SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); - } - - return *mPattern; - } - - union { - AlignedStorage2<ColorPattern> mColorPattern; - AlignedStorage2<LinearGradientPattern> mLinearGradientPattern; - AlignedStorage2<RadialGradientPattern> mRadialGradientPattern; - AlignedStorage2<SurfacePattern> mSurfacePattern; - }; - Pattern *mPattern; -}; - -/* This is an RAII based class that can be used as a drawtarget for - * operations that need a shadow drawn. It will automatically provide a - * temporary target when needed, and if so blend it back with a shadow. - * - * aBounds specifies the bounds of the drawing operation that will be - * drawn to the target, it is given in device space! This function will - * change aBounds to incorporate shadow bounds. If this is NULL the drawing - * operation will be assumed to cover an infinite rect. - */ -class AdjustedTarget -{ -public: - typedef CanvasRenderingContext2D::ContextState ContextState; - - AdjustedTarget(CanvasRenderingContext2D *ctx, - mgfx::Rect *aBounds = nullptr) - : mCtx(nullptr) - { - if (!ctx->NeedToDrawShadow()) { - mTarget = ctx->mTarget; - return; - } - mCtx = ctx; - - const ContextState &state = mCtx->CurrentState(); - - mSigma = state.shadowBlur / 2.0f; - - if (mSigma > SIGMA_MAX) { - mSigma = SIGMA_MAX; - } - - Matrix transform = mCtx->mTarget->GetTransform(); - - mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); - - static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; - int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); - - // We need to enlarge and possibly offset our temporary surface - // so that things outside of the canvas may cast shadows. - mTempRect.Inflate(Margin(blurRadius + NS_MAX<Float>(state.shadowOffset.x, 0), - blurRadius + NS_MAX<Float>(state.shadowOffset.y, 0), - blurRadius + NS_MAX<Float>(-state.shadowOffset.x, 0), - blurRadius + NS_MAX<Float>(-state.shadowOffset.y, 0))); - - if (aBounds) { - // We actually include the bounds of the shadow blur, this makes it - // easier to execute the actual blur on hardware, and shouldn't affect - // the amount of pixels that need to be touched. - aBounds->Inflate(Margin(blurRadius, blurRadius, - blurRadius, blurRadius)); - mTempRect = mTempRect.Intersect(*aBounds); - } - - mTempRect.ScaleRoundOut(1.0f); - - transform._31 -= mTempRect.x; - transform._32 -= mTempRect.y; - - mTarget = - mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), - FORMAT_B8G8R8A8, mSigma); - - if (!mTarget) { - // XXX - Deal with the situation where our temp size is too big to - // fit in a texture. - mTarget = ctx->mTarget; - mCtx = nullptr; - } else { - mTarget->SetTransform(transform); - } - } - - ~AdjustedTarget() - { - if (!mCtx) { - return; - } - - RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); - - mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), - Color::FromABGR(mCtx->CurrentState().shadowColor), - mCtx->CurrentState().shadowOffset, mSigma, - mCtx->CurrentState().op); - } - - DrawTarget* operator->() - { - return mTarget; - } - -private: - RefPtr<DrawTarget> mTarget; - CanvasRenderingContext2D *mCtx; - Float mSigma; - mgfx::Rect mTempRect; -}; - -NS_IMETHODIMP -CanvasGradient::AddColorStop(float offset, const nsAString& colorstr) -{ - if (!FloatValidate(offset) || offset < 0.0 || offset > 1.0) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - nsCSSValue value; - nsCSSParser parser; - if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - nscolor color; - if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - mStops = nullptr; - - GradientStop newStop; - - newStop.offset = offset; - newStop.color = Color::FromABGR(color); - - mRawStops.AppendElement(newStop); - - return NS_OK; -} - -NS_DEFINE_STATIC_IID_ACCESSOR(CanvasGradient, NS_CANVASGRADIENTAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(CanvasGradient) -NS_IMPL_RELEASE(CanvasGradient) - -NS_INTERFACE_MAP_BEGIN(CanvasGradient) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasGradient) - NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasGradient) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -NS_DEFINE_STATIC_IID_ACCESSOR(CanvasPattern, NS_CANVASPATTERNAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(CanvasPattern) -NS_IMPL_RELEASE(CanvasPattern) - -NS_INTERFACE_MAP_BEGIN(CanvasPattern) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasPattern) - NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasPattern) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -/** - ** TextMetrics - **/ -class TextMetrics : public nsIDOMTextMetrics -{ -public: - TextMetrics(float w) : width(w) { } - - virtual ~TextMetrics() { } - - NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXTMETRICSAZURE_PRIVATE_IID) - - NS_IMETHOD GetWidth(float* w) - { - *w = width; - return NS_OK; - } - - NS_DECL_ISUPPORTS - -private: - float width; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(TextMetrics, NS_TEXTMETRICSAZURE_PRIVATE_IID) - -NS_IMPL_ADDREF(TextMetrics) -NS_IMPL_RELEASE(TextMetrics) - -NS_INTERFACE_MAP_BEGIN(TextMetrics) - NS_INTERFACE_MAP_ENTRY(mozilla::dom::TextMetrics) - NS_INTERFACE_MAP_ENTRY(nsIDOMTextMetrics) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TextMetrics) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -class CanvasRenderingContext2DUserData : public LayerUserData { -public: - CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) - : mContext(aContext) - { - aContext->mUserDatas.AppendElement(this); - } - ~CanvasRenderingContext2DUserData() - { - if (mContext) { - mContext->mUserDatas.RemoveElement(this); - } - } - static void DidTransactionCallback(void* aData) - { - CanvasRenderingContext2DUserData* self = - static_cast<CanvasRenderingContext2DUserData*>(aData); - if (self->mContext) { - self->mContext->MarkContextClean(); - } - } - bool IsForContext(CanvasRenderingContext2D *aContext) - { - return mContext == aContext; - } - void Forget() - { - mContext = nullptr; - } - -private: - CanvasRenderingContext2D *mContext; -}; - -NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) -NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) - -NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCanvasElement) - NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CanvasRenderingContext2D) - NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_TRACE_END -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mCanvasElement, nsINode) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) - if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { - dom::Element* canvasElement = tmp->mCanvasElement; - if (canvasElement) { - if (canvasElement->IsPurple()) { - canvasElement->RemovePurple(); - } - dom::Element::MarkNodeChildren(canvasElement); - } - return true; - } -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END - -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) - return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END - -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) - return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); -NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -/** - ** CanvasRenderingContext2D impl - **/ - - -// Initialize our static variables. -uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; -uint8_t (*CanvasRenderingContext2D::sUnpremultiplyTable)[256] = nullptr; -uint8_t (*CanvasRenderingContext2D::sPremultiplyTable)[256] = nullptr; -DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; - - - -CanvasRenderingContext2D::CanvasRenderingContext2D() - : mZero(false), mOpaque(false), mResetLayer(true) - , mIPC(false) - , mIsEntireFrameInvalid(false) - , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) - , mInvalidateCount(0) -{ - sNumLivingContexts++; - SetIsDOMBinding(); -} - -CanvasRenderingContext2D::~CanvasRenderingContext2D() -{ - Reset(); - // Drop references from all CanvasRenderingContext2DUserData to this context - for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { - mUserDatas[i]->Forget(); - } - sNumLivingContexts--; - if (!sNumLivingContexts) { - delete[] sUnpremultiplyTable; - delete[] sPremultiplyTable; - sUnpremultiplyTable = nullptr; - sPremultiplyTable = nullptr; - NS_IF_RELEASE(sErrorTarget); - } -} - -JSObject* -CanvasRenderingContext2D::WrapObject(JSContext *cx, JSObject *scope, - bool *triedToWrap) -{ - return CanvasRenderingContext2DBinding::Wrap(cx, scope, this, triedToWrap); -} - -bool -CanvasRenderingContext2D::ParseColor(const nsAString& aString, - nscolor* aColor) -{ - nsIDocument* document = mCanvasElement - ? mCanvasElement->OwnerDoc() - : nullptr; - - // Pass the CSS Loader object to the parser, to allow parser error - // reports to include the outer window ID. - nsCSSParser parser(document ? document->CSSLoader() : nullptr); - nsCSSValue value; - if (!parser.ParseColorString(aString, nullptr, 0, value)) { - return false; - } - - if (value.GetUnit() == nsCSSUnit::eCSSUnit_Color) { - // if we already have a color we can just use it directly - *aColor = value.GetColorValue(); - } else { - // otherwise resolve it - nsIPresShell* presShell = GetPresShell(); - nsRefPtr<nsStyleContext> parentContext; - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // Inherit from the canvas element. - parentContext = nsComputedDOMStyle::GetStyleContextForElement( - mCanvasElement, nullptr, presShell); - } - - unused << nsRuleNode::ComputeColor( - value, presShell ? presShell->GetPresContext() : nullptr, parentContext, - *aColor); - } - return true; -} - -nsresult -CanvasRenderingContext2D::Reset() -{ - if (mCanvasElement) { - mCanvasElement->InvalidateCanvas(); - } - - // only do this for non-docshell created contexts, - // since those are the ones that we created a surface for - if (mTarget && IsTargetValid() && !mDocShell) { - gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; - } - - mTarget = nullptr; - - // Since the target changes the backing texture will change, and this will - // no longer be valid. - mThebesSurface = nullptr; - mIsEntireFrameInvalid = false; - mPredictManyRedrawCalls = false; - - return NS_OK; -} - -static void -WarnAboutUnexpectedStyle(nsHTMLCanvasElement* canvasElement) -{ - nsContentUtils::ReportToConsole( - nsIScriptError::warningFlag, - "Canvas", - canvasElement ? canvasElement->OwnerDoc() : nullptr, - nsContentUtils::eDOM_PROPERTIES, - "UnexpectedCanvasVariantStyle"); -} - -void -CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, - Style whichStyle) -{ - MOZ_ASSERT(!str.IsVoid()); - - nscolor color; - if (!ParseColor(str, &color)) { - return; - } - - CurrentState().SetColorStyle(whichStyle, color); -} - -nsISupports* -CanvasRenderingContext2D::GetStyleAsStringOrInterface(nsAString& aStr, - CanvasMultiGetterType& aType, - Style aWhichStyle) -{ - const ContextState &state = CurrentState(); - nsISupports* supports; - if (state.patternStyles[aWhichStyle]) { - aStr.SetIsVoid(true); - supports = state.patternStyles[aWhichStyle]; - aType = CMG_STYLE_PATTERN; - } else if (state.gradientStyles[aWhichStyle]) { - aStr.SetIsVoid(true); - supports = state.gradientStyles[aWhichStyle]; - aType = CMG_STYLE_GRADIENT; - } else { - StyleColorToString(state.colorStyles[aWhichStyle], aStr); - supports = nullptr; - aType = CMG_STYLE_STRING; - } - return supports; -} - -// static -void -CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) -{ - // We can't reuse the normal CSS color stringification code, - // because the spec calls for a different algorithm for canvas. - if (NS_GET_A(aColor) == 255) { - CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", - NS_GET_R(aColor), - NS_GET_G(aColor), - NS_GET_B(aColor)), - aStr); - } else { - CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", - NS_GET_R(aColor), - NS_GET_G(aColor), - NS_GET_B(aColor)), - aStr); - aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); - aStr.Append(')'); - } -} - -nsresult -CanvasRenderingContext2D::Redraw() -{ - if (mIsEntireFrameInvalid) { - return NS_OK; - } - - mIsEntireFrameInvalid = true; - - if (!mCanvasElement) { - NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return NS_OK; - } - - if (!mThebesSurface) - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - mThebesSurface->MarkDirty(); - - nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(nullptr); - - return NS_OK; -} - -void -CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) -{ - ++mInvalidateCount; - - if (mIsEntireFrameInvalid) { - return; - } - - if (mPredictManyRedrawCalls || - mInvalidateCount > kCanvasMaxInvalidateCount) { - Redraw(); - return; - } - - if (!mCanvasElement) { - NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); - return; - } - - if (!mThebesSurface) - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - mThebesSurface->MarkDirty(); - - nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); - - mCanvasElement->InvalidateCanvasContent(&r); -} - -void -CanvasRenderingContext2D::RedrawUser(const gfxRect& r) -{ - if (mIsEntireFrameInvalid) { - ++mInvalidateCount; - return; - } - - mgfx::Rect newr = - mTarget->GetTransform().TransformBounds(ToRect(r)); - Redraw(newr); -} - -void -CanvasRenderingContext2D::EnsureTarget() -{ - if (mTarget) { - return; - } - - // Check that the dimensions are sane - IntSize size(mWidth, mHeight); - if (size.width <= 0xFFFF && size.height <= 0xFFFF && - size.width >= 0 && size.height >= 0) { - SurfaceFormat format = GetSurfaceFormat(); - nsIDocument* ownerDoc = nullptr; - if (mCanvasElement) { - ownerDoc = mCanvasElement->OwnerDoc(); - } - - nsRefPtr<LayerManager> layerManager = nullptr; - - if (ownerDoc) { - layerManager = - nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); - } - - if (layerManager) { - mTarget = layerManager->CreateDrawTarget(size, format); - } else { - mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format); - } - } - - if (mTarget) { - if (gCanvasAzureMemoryReporter == nullptr) { - gCanvasAzureMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasAzureMemory); - NS_RegisterMemoryReporter(gCanvasAzureMemoryReporter); - } - - gCanvasAzureMemoryUsed += mWidth * mHeight * 4; - JSContext* context = nsContentUtils::GetCurrentJSContext(); - if (context) { - JS_updateMallocCounter(context, mWidth * mHeight * 4); - } - - mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); - // Force a full layer transaction since we didn't have a layer before - // and now we might need one. - if (mCanvasElement) { - mCanvasElement->InvalidateCanvas(); - } - // Calling Redraw() tells our invalidation machinery that the entire - // canvas is already invalid, which can speed up future drawing. - Redraw(); - } else { - EnsureErrorTarget(); - mTarget = sErrorTarget; - } -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) -{ - ClearTarget(); - - // Zero sized surfaces cause issues, so just go with 1x1. - if (height == 0 || width == 0) { - mZero = true; - mWidth = 1; - mHeight = 1; - } else { - mZero = false; - mWidth = width; - mHeight = height; - } - - return NS_OK; -} - -void -CanvasRenderingContext2D::ClearTarget() -{ - Reset(); - - mResetLayer = true; - - // set up the initial canvas defaults - mStyleStack.Clear(); - mPathBuilder = nullptr; - mPath = nullptr; - mDSPathBuilder = nullptr; - - ContextState *state = mStyleStack.AppendElement(); - state->globalAlpha = 1.0; - - state->colorStyles[STYLE_FILL] = NS_RGB(0,0,0); - state->colorStyles[STYLE_STROKE] = NS_RGB(0,0,0); - state->shadowColor = NS_RGBA(0,0,0,0); -} - -NS_IMETHODIMP -CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, - gfxASurface *surface, - int32_t width, - int32_t height) -{ - mDocShell = shell; - mThebesSurface = surface; - - SetDimensions(width, height); - mTarget = gfxPlatform::GetPlatform()-> - CreateDrawTargetForSurface(surface, IntSize(width, height)); - if (!mTarget) { - EnsureErrorTarget(); - mTarget = sErrorTarget; - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) -{ - if (isOpaque != mOpaque) { - mOpaque = isOpaque; - ClearTarget(); - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::SetIsIPC(bool isIPC) -{ - if (isIPC != mIPC) { - mIPC = isIPC; - ClearTarget(); - } - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, uint32_t aFlags) -{ - nsresult rv = NS_OK; - - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr<gfxASurface> surface; - - if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { - return NS_ERROR_FAILURE; - } - - nsRefPtr<gfxPattern> pat = new gfxPattern(surface); - - pat->SetFilter(aFilter); - pat->SetExtend(gfxPattern::EXTEND_PAD); - - gfxContext::GraphicsOperator op = ctx->CurrentOperator(); - if (mOpaque) - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - - // XXX I don't want to use PixelSnapped here, but layout doesn't guarantee - // pixel alignment for this stuff! - ctx->NewPath(); - ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat); - ctx->Fill(); - - if (mOpaque) - ctx->SetOperator(op); - - if (!(aFlags & RenderFlagPremultAlpha)) { - nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface(); - nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface(); - NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!"); - - gfxUtils::UnpremultiplyImageSurface(gis); - } - - return rv; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::GetInputStream(const char *aMimeType, - const PRUnichar *aEncoderOptions, - nsIInputStream **aStream) -{ - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr<gfxASurface> surface; - - if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { - return NS_ERROR_FAILURE; - } - - nsresult rv; - const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; - nsAutoArrayPtr<char> conid(new (std::nothrow) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); - - if (!conid) { - return NS_ERROR_OUT_OF_MEMORY; - } - - strcpy(conid, encoderPrefix); - strcat(conid, aMimeType); - - nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid); - if (!encoder) { - return NS_ERROR_FAILURE; - } - - nsAutoArrayPtr<uint8_t> imageBuffer(new (std::nothrow) uint8_t[mWidth * mHeight * 4]); - if (!imageBuffer) { - return NS_ERROR_OUT_OF_MEMORY; - } - - nsRefPtr<gfxImageSurface> imgsurf = - new gfxImageSurface(imageBuffer.get(), - gfxIntSize(mWidth, mHeight), - mWidth * 4, - gfxASurface::ImageFormatARGB32); - - if (!imgsurf || imgsurf->CairoStatus()) { - return NS_ERROR_FAILURE; - } - - nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf); - - if (!ctx || ctx->HasError()) { - return NS_ERROR_FAILURE; - } - - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(surface, gfxPoint(0, 0)); - ctx->Paint(); - - rv = encoder->InitFromData(imageBuffer.get(), - mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4, - imgIEncoder::INPUT_FORMAT_HOSTARGB, - nsDependentString(aEncoderOptions)); - NS_ENSURE_SUCCESS(rv, rv); - - return CallQueryInterface(encoder, aStream); -} - -SurfaceFormat -CanvasRenderingContext2D::GetSurfaceFormat() const -{ - return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8; -} - -// -// state -// - -void -CanvasRenderingContext2D::Save() -{ - EnsureTarget(); - mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); - mStyleStack.SetCapacity(mStyleStack.Length() + 1); - mStyleStack.AppendElement(CurrentState()); -} - -void -CanvasRenderingContext2D::Restore() -{ - if (mStyleStack.Length() - 1 == 0) - return; - - TransformWillUpdate(); - - for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { - mTarget->PopClip(); - } - - mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); - - mTarget->SetTransform(CurrentState().transform); -} - -// -// transformations -// - -void -CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) -{ - if (!FloatValidate(x,y)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newMatrix = mTarget->GetTransform(); - mTarget->SetTransform(newMatrix.Scale(x, y)); -} - -void -CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) -{ - if (!FloatValidate(angle)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - - Matrix rotation = Matrix::Rotation(angle); - mTarget->SetTransform(rotation * mTarget->GetTransform()); -} - -void -CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) -{ - if (!FloatValidate(x,y)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newMatrix = mTarget->GetTransform(); - mTarget->SetTransform(newMatrix.Translate(x, y)); -} - -void -CanvasRenderingContext2D::Transform(double m11, double m12, double m21, - double m22, double dx, double dy, - ErrorResult& error) -{ - if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix matrix(m11, m12, m21, m22, dx, dy); - mTarget->SetTransform(matrix * mTarget->GetTransform()); -} - -void -CanvasRenderingContext2D::SetTransform(double m11, double m12, - double m21, double m22, - double dx, double dy, - ErrorResult& error) -{ - if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { - return; - } - - TransformWillUpdate(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix matrix(m11, m12, m21, m22, dx, dy); - mTarget->SetTransform(matrix); -} - -JSObject* -MatrixToJSObject(JSContext* cx, const Matrix& matrix, ErrorResult& error) -{ - jsval elts[] = { - DOUBLE_TO_JSVAL(matrix._11), DOUBLE_TO_JSVAL(matrix._12), - DOUBLE_TO_JSVAL(matrix._21), DOUBLE_TO_JSVAL(matrix._22), - DOUBLE_TO_JSVAL(matrix._31), DOUBLE_TO_JSVAL(matrix._32) - }; - - // XXX Should we enter GetWrapper()'s compartment? - JSObject* obj = JS_NewArrayObject(cx, 6, elts); - if (!obj) { - error.Throw(NS_ERROR_OUT_OF_MEMORY); - } - return obj; -} - -bool -ObjectToMatrix(JSContext* cx, JSObject& obj, Matrix& matrix, ErrorResult& error) -{ - uint32_t length; - if (!JS_GetArrayLength(cx, &obj, &length) || length != 6) { - // Not an array-like thing or wrong size - error.Throw(NS_ERROR_INVALID_ARG); - return false; - } - - Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, - &matrix._31, &matrix._32 }; - for (uint32_t i = 0; i < 6; ++i) { - jsval elt; - double d; - if (!JS_GetElement(cx, &obj, i, &elt)) { - error.Throw(NS_ERROR_FAILURE); - return false; - } - if (!CoerceDouble(elt, &d)) { - error.Throw(NS_ERROR_INVALID_ARG); - return false; - } - if (!FloatValidate(d)) { - // This is weird, but it's the behavior of SetTransform() - return false; - } - *elts[i] = Float(d); - } - return true; -} - -void -CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, - JSObject& currentTransform, - ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newCTM; - if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { - mTarget->SetTransform(newCTM); - } -} - -JSObject* -CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, - ErrorResult& error) const -{ - return MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), error); -} - -void -CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, - JSObject& currentTransform, - ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - Matrix newCTMInverse; - if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - if (newCTMInverse.Invert()) { - mTarget->SetTransform(newCTMInverse); - } - } -} - -JSObject* -CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, - ErrorResult& error) const -{ - if (!mTarget) { - return MatrixToJSObject(cx, Matrix(), error); - } - - Matrix ctm = mTarget->GetTransform(); - - if (!ctm.Invert()) { - double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); - ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); - } - - return MatrixToJSObject(cx, ctm, error); -} - -// -// colors -// - -void -CanvasRenderingContext2D::SetStyleFromJSValue(JSContext* cx, - JS::Value& value, - Style whichStyle) -{ - if (value.isString()) { - nsDependentJSString strokeStyle; - if (strokeStyle.init(cx, value.toString())) { - SetStyleFromString(strokeStyle, whichStyle); - } - return; - } - - if (value.isObject()) { - nsCOMPtr<nsISupports> holder; - - CanvasGradient* gradient; - nsresult rv = xpc_qsUnwrapArg<CanvasGradient>(cx, value, &gradient, - static_cast<nsISupports**>(getter_AddRefs(holder)), - &value); - if (NS_SUCCEEDED(rv)) { - SetStyleFromGradient(gradient, whichStyle); - return; - } - - CanvasPattern* pattern; - rv = xpc_qsUnwrapArg<CanvasPattern>(cx, value, &pattern, - static_cast<nsISupports**>(getter_AddRefs(holder)), - &value); - if (NS_SUCCEEDED(rv)) { - SetStyleFromPattern(pattern, whichStyle); - return; - } - } - - WarnAboutUnexpectedStyle(mCanvasElement); -} - -static JS::Value -WrapStyle(JSContext* cx, JSObject* obj, - CanvasRenderingContext2D::CanvasMultiGetterType type, - nsAString& str, nsISupports* supports, ErrorResult& error) -{ - JS::Value v; - bool ok; - switch (type) { - case CanvasRenderingContext2D::CMG_STYLE_STRING: - { - ok = xpc::StringToJsval(cx, str, &v); - break; - } - case CanvasRenderingContext2D::CMG_STYLE_PATTERN: - case CanvasRenderingContext2D::CMG_STYLE_GRADIENT: - { - ok = dom::WrapObject(cx, obj, supports, &v); - break; - } - default: - MOZ_NOT_REACHED("unexpected CanvasMultiGetterType"); - } - if (!ok) { - error.Throw(NS_ERROR_FAILURE); - } - return v; -} - - -JS::Value -CanvasRenderingContext2D::GetStrokeStyle(JSContext* cx, - ErrorResult& error) -{ - nsString str; - CanvasMultiGetterType t; - nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_STROKE); - return WrapStyle(cx, GetWrapper(), t, str, supports, error); -} - -JS::Value -CanvasRenderingContext2D::GetFillStyle(JSContext* cx, - ErrorResult& error) -{ - nsString str; - CanvasMultiGetterType t; - nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_FILL); - return WrapStyle(cx, GetWrapper(), t, str, supports, error); -} - -void -CanvasRenderingContext2D::SetFillRule(const nsAString& aString) -{ - FillRule rule; - - if (aString.EqualsLiteral("evenodd")) - rule = FILL_EVEN_ODD; - else if (aString.EqualsLiteral("nonzero")) - rule = FILL_WINDING; - else - return; - - CurrentState().fillRule = rule; -} - -void -CanvasRenderingContext2D::GetFillRule(nsAString& aString) -{ - switch (CurrentState().fillRule) { - case FILL_WINDING: - aString.AssignLiteral("nonzero"); break; - case FILL_EVEN_ODD: - aString.AssignLiteral("evenodd"); break; - } -} -// -// gradients and patterns -// -already_AddRefed<nsIDOMCanvasGradient> -CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1, - ErrorResult& aError) -{ - if (!FloatValidate(x0,y0,x1,y1)) { - aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return nullptr; - } - - nsRefPtr<nsIDOMCanvasGradient> grad = - new CanvasLinearGradient(Point(x0, y0), Point(x1, y1)); - - return grad.forget(); -} - -already_AddRefed<nsIDOMCanvasGradient> -CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, - double x1, double y1, double r1, - ErrorResult& aError) -{ - if (!FloatValidate(x0,y0,r0,x1,y1,r1)) { - aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return nullptr; - } - - if (r0 < 0.0 || r1 < 0.0) { - aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return nullptr; - } - - nsRefPtr<nsIDOMCanvasGradient> grad = - new CanvasRadialGradient(Point(x0, y0), r0, Point(x1, y1), r1); - - return grad.forget(); -} - -already_AddRefed<nsIDOMCanvasPattern> -CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, - const nsAString& repeat, - ErrorResult& error) -{ - CanvasPattern::RepeatMode repeatMode = - CanvasPattern::NOREPEAT; - - if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { - repeatMode = CanvasPattern::REPEAT; - } else if (repeat.EqualsLiteral("repeat-x")) { - repeatMode = CanvasPattern::REPEATX; - } else if (repeat.EqualsLiteral("repeat-y")) { - repeatMode = CanvasPattern::REPEATY; - } else if (repeat.EqualsLiteral("no-repeat")) { - repeatMode = CanvasPattern::NOREPEAT; - } else { - error.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return NULL; - } - - Element* htmlElement; - if (element.IsHTMLCanvasElement()) { - nsHTMLCanvasElement* canvas = element.GetAsHTMLCanvasElement(); - htmlElement = canvas; - - nsIntSize size = canvas->GetSize(); - if (size.width == 0 || size.height == 0) { - error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return NULL; - } - - // Special case for Canvas, which could be an Azure canvas! - nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); - if (srcCanvas) { - // This might not be an Azure canvas! - RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); - - nsRefPtr<CanvasPattern> pat = - new CanvasPattern(srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); - - return pat.forget(); - } - } else if (element.IsHTMLImageElement()) { - htmlElement = element.GetAsHTMLImageElement(); - } else { - htmlElement = element.GetAsHTMLVideoElement(); - } - - // The canvas spec says that createPattern should use the first frame - // of animated images - nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(htmlElement, - nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE); - - if (!res.mSurface) { - error.Throw(NS_ERROR_NOT_AVAILABLE); - return NULL; - } - - // Ignore nullptr cairo surfaces! See bug 666312. - if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) { - return NULL; - } - - EnsureTarget(); - RefPtr<SourceSurface> srcSurf = - gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); - - nsRefPtr<CanvasPattern> pat = - new CanvasPattern(srcSurf, repeatMode, res.mPrincipal, - res.mIsWriteOnly, res.mCORSUsed); - - return pat.forget(); -} - -// -// shadows -// -void -CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) -{ - nscolor color; - if (!ParseColor(shadowColor, &color)) { - return; - } - - CurrentState().shadowColor = color; -} - -// -// rects -// - -void -CanvasRenderingContext2D::ClearRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h) || !mTarget) { - return; - } - - mTarget->ClearRect(mgfx::Rect(x, y, w, h)); - - RedrawUser(gfxRect(x, y, w, h)); -} - -void -CanvasRenderingContext2D::FillRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h)) { - return; - } - - const ContextState &state = CurrentState(); - - if (state.patternStyles[STYLE_FILL]) { - CanvasPattern::RepeatMode repeat = - state.patternStyles[STYLE_FILL]->mRepeat; - // In the FillRect case repeat modes are easy to deal with. - bool limitx = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATY; - bool limity = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATX; - - IntSize patternSize = - state.patternStyles[STYLE_FILL]->mSurface->GetSize(); - - // We always need to execute painting for non-over operators, even if - // we end up with w/h = 0. - if (limitx) { - if (x < 0) { - w += x; - if (w < 0) { - w = 0; - } - - x = 0; - } - if (x + w > patternSize.width) { - w = patternSize.width - x; - if (w < 0) { - w = 0; - } - } - } - if (limity) { - if (y < 0) { - h += y; - if (h < 0) { - h = 0; - } - - y = 0; - } - if (y + h > patternSize.height) { - h = patternSize.height - y; - if (h < 0) { - h = 0; - } - } - } - } - - mgfx::Rect bounds; - - EnsureTarget(); - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(x, y, w, h); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - FillRect(mgfx::Rect(x, y, w, h), - CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), - DrawOptions(state.globalAlpha, UsedOperation())); - - RedrawUser(gfxRect(x, y, w, h)); -} - -void -CanvasRenderingContext2D::StrokeRect(double x, double y, double w, - double h) -{ - if (!FloatValidate(x,y,w,h)) { - return; - } - - const ContextState &state = CurrentState(); - - mgfx::Rect bounds; - - if (!w && !h) { - return; - } - - EnsureTarget(); - if (!IsTargetValid()) { - return; - } - - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, - w + state.lineWidth, h + state.lineWidth); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - if (!h) { - CapStyle cap = CAP_BUTT; - if (state.lineJoin == JOIN_ROUND) { - cap = CAP_ROUND; - } - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeLine(Point(x, y), Point(x + w, y), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - cap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - return; - } - - if (!w) { - CapStyle cap = CAP_BUTT; - if (state.lineJoin == JOIN_ROUND) { - cap = CAP_ROUND; - } - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeLine(Point(x, y), Point(x, y + h), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - cap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - return; - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - StrokeRect(mgfx::Rect(x, y, w, h), - CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, UsedOperation())); - - Redraw(); -} - -// -// path bits -// - -void -CanvasRenderingContext2D::BeginPath() -{ - mPath = nullptr; - mPathBuilder = nullptr; - mDSPathBuilder = nullptr; - mPathTransformWillUpdate = false; -} - -void -CanvasRenderingContext2D::Fill() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - mgfx::Rect bounds; - - if (NeedToDrawShadow()) { - bounds = mPath->GetBounds(mTarget->GetTransform()); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - Fill(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), - DrawOptions(CurrentState().globalAlpha, UsedOperation())); - - Redraw(); -} - -void -CanvasRenderingContext2D::Stroke() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - const ContextState &state = CurrentState(); - - StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), state.dash.Elements(), - state.dashOffset); - - mgfx::Rect bounds; - if (NeedToDrawShadow()) { - bounds = - mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - Stroke(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), - strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); - - Redraw(); -} - -void -CanvasRenderingContext2D::Clip() -{ - EnsureUserSpacePath(); - - if (!mPath) { - return; - } - - mTarget->PushClip(mPath); - CurrentState().clipsPushed.push_back(mPath); -} - -void -CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, - double y2, double radius, - ErrorResult& error) -{ - if (!FloatValidate(x1, y1, x2, y2, radius)) { - return; - } - - if (radius < 0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - EnsureWritablePath(); - - // Current point in user space! - Point p0; - if (mPathBuilder) { - p0 = mPathBuilder->CurrentPoint(); - } else { - Matrix invTransform = mTarget->GetTransform(); - if (!invTransform.Invert()) { - return; - } - - p0 = invTransform * mDSPathBuilder->CurrentPoint(); - } - - Point p1(x1, y1); - Point p2(x2, y2); - - // Execute these calculations in double precision to avoid cumulative - // rounding errors. - double dir, a2, b2, c2, cosx, sinx, d, anx, any, - bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; - bool anticlockwise; - - if (p0 == p1 || p1 == p2 || radius == 0) { - LineTo(p1.x, p1.y); - return; - } - - // Check for colinearity - dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); - if (dir == 0) { - LineTo(p1.x, p1.y); - return; - } - - - // XXX - Math for this code was already available from the non-azure code - // and would be well tested. Perhaps converting to bezier directly might - // be more efficient longer run. - a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); - b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); - c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); - cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); - - sinx = sqrt(1 - cosx*cosx); - d = radius / ((1 - cosx) / sinx); - - anx = (x1-p0.x) / sqrt(a2); - any = (y1-p0.y) / sqrt(a2); - bnx = (x1-x2) / sqrt(b2); - bny = (y1-y2) / sqrt(b2); - x3 = x1 - anx*d; - y3 = y1 - any*d; - x4 = x1 - bnx*d; - y4 = y1 - bny*d; - anticlockwise = (dir < 0); - cx = x3 + any*radius*(anticlockwise ? 1 : -1); - cy = y3 - anx*radius*(anticlockwise ? 1 : -1); - angle0 = atan2((y3-cy), (x3-cx)); - angle1 = atan2((y4-cy), (x4-cx)); - - - LineTo(x3, y3); - - Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); -} - -void -CanvasRenderingContext2D::Arc(double x, double y, double r, - double startAngle, double endAngle, - bool anticlockwise, ErrorResult& error) -{ - if (!FloatValidate(x, y, r, startAngle, endAngle)) { - return; - } - - if (r < 0.0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - EnsureWritablePath(); - - ArcToBezier(this, Point(x, y), r, startAngle, endAngle, anticlockwise); -} - -void -CanvasRenderingContext2D::Rect(double x, double y, double w, double h) -{ - if (!FloatValidate(x, y, w, h)) { - return; - } - - EnsureWritablePath(); - - if (mPathBuilder) { - mPathBuilder->MoveTo(Point(x, y)); - mPathBuilder->LineTo(Point(x + w, y)); - mPathBuilder->LineTo(Point(x + w, y + h)); - mPathBuilder->LineTo(Point(x, y + h)); - mPathBuilder->Close(); - } else { - mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); - mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); - mDSPathBuilder->Close(); - } -} - -void -CanvasRenderingContext2D::EnsureWritablePath() -{ - if (mDSPathBuilder) { - return; - } - - FillRule fillRule = CurrentState().fillRule; - - if (mPathBuilder) { - if (mPathTransformWillUpdate) { - mPath = mPathBuilder->Finish(); - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPath = nullptr; - mPathBuilder = nullptr; - mPathTransformWillUpdate = false; - } - return; - } - - EnsureTarget(); - if (!mPath) { - NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); - mPathBuilder = mTarget->CreatePathBuilder(fillRule); - } else if (!mPathTransformWillUpdate) { - mPathBuilder = mPath->CopyToBuilder(fillRule); - } else { - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPathTransformWillUpdate = false; - } -} - -void -CanvasRenderingContext2D::EnsureUserSpacePath(bool aCommitTransform /* = true */) -{ - FillRule fillRule = CurrentState().fillRule; - - if (!mPath && !mPathBuilder && !mDSPathBuilder) { - EnsureTarget(); - mPathBuilder = mTarget->CreatePathBuilder(fillRule); - } - - if (mPathBuilder) { - mPath = mPathBuilder->Finish(); - mPathBuilder = nullptr; - } - - if (aCommitTransform && - mPath && - mPathTransformWillUpdate) { - mDSPathBuilder = - mPath->TransformedCopyToBuilder(mPathToDS, fillRule); - mPath = nullptr; - mPathTransformWillUpdate = false; - } - - if (mDSPathBuilder) { - RefPtr<Path> dsPath; - dsPath = mDSPathBuilder->Finish(); - mDSPathBuilder = nullptr; - - Matrix inverse = mTarget->GetTransform(); - if (!inverse.Invert()) { - NS_WARNING("Could not invert transform"); - return; - } - - mPathBuilder = - dsPath->TransformedCopyToBuilder(inverse, fillRule); - mPath = mPathBuilder->Finish(); - mPathBuilder = nullptr; - } - - if (mPath && mPath->GetFillRule() != fillRule) { - mPathBuilder = mPath->CopyToBuilder(fillRule); - mPath = mPathBuilder->Finish(); - } - - NS_ASSERTION(mPath, "mPath should exist"); -} - -void -CanvasRenderingContext2D::TransformWillUpdate() -{ - EnsureTarget(); - - // Store the matrix that would transform the current path to device - // space. - if (mPath || mPathBuilder) { - if (!mPathTransformWillUpdate) { - // If the transform has already been updated, but a device space builder - // has not been created yet mPathToDS contains the right transform to - // transform the current mPath into device space. - // We should leave it alone. - mPathToDS = mTarget->GetTransform(); - } - mPathTransformWillUpdate = true; - } -} - -// -// text -// - -/** - * Helper function for SetFont that creates a style rule for the given font. - * @param aFont The CSS font string - * @param aNode The canvas element - * @param aResult Pointer in which to place the new style rule. - * @remark Assumes all pointer arguments are non-null. - */ -static nsresult -CreateFontStyleRule(const nsAString& aFont, - nsINode* aNode, - StyleRule** aResult) -{ - nsRefPtr<StyleRule> rule; - bool changed; - - nsIPrincipal* principal = aNode->NodePrincipal(); - nsIDocument* document = aNode->OwnerDoc(); - - nsIURI* docURL = document->GetDocumentURI(); - nsIURI* baseURL = document->GetDocBaseURI(); - - // Pass the CSS Loader object to the parser, to allow parser error reports - // to include the outer window ID. - nsCSSParser parser(document->CSSLoader()); - - nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, - principal, getter_AddRefs(rule)); - if (NS_FAILED(rv)) { - return rv; - } - - rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, - principal, rule->GetDeclaration(), &changed, - false); - if (NS_FAILED(rv)) - return rv; - - rv = parser.ParseProperty(eCSSProperty_line_height, - NS_LITERAL_STRING("normal"), docURL, baseURL, - principal, rule->GetDeclaration(), &changed, - false); - if (NS_FAILED(rv)) { - return rv; - } - - rule->RuleMatched(); - - rule.forget(aResult); - return NS_OK; -} - -void -CanvasRenderingContext2D::SetFont(const nsAString& font, - ErrorResult& error) -{ - /* - * If font is defined with relative units (e.g. ems) and the parent - * style context changes in between calls, setting the font to the - * same value as previous could result in a different computed value, - * so we cannot have the optimization where we check if the new font - * string is equal to the old one. - */ - - if (!mCanvasElement && !mDocShell) { - NS_WARNING("Canvas element must be non-null or a docshell must be provided"); - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsIPresShell* presShell = GetPresShell(); - if (!presShell) { - error.Throw(NS_ERROR_FAILURE); - return; - } - nsIDocument* document = presShell->GetDocument(); - - nsRefPtr<css::StyleRule> rule; - error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); - - if (error.Failed()) { - return; - } - - css::Declaration *declaration = rule->GetDeclaration(); - // The easiest way to see whether we got a syntax error or whether - // we got 'inherit' or 'initial' is to look at font-size-adjust, - // which the shorthand resets to either 'none' or - // '-moz-system-font'. - // We know the declaration is not !important, so we can use - // GetNormalBlock(). - const nsCSSValue *fsaVal = - declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); - if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && - fsaVal->GetUnit() != eCSSUnit_System_Font)) { - // We got an all-property value or a syntax error. The spec says - // this value must be ignored. - return; - } - - nsTArray< nsCOMPtr<nsIStyleRule> > rules; - rules.AppendElement(rule); - - nsStyleSet* styleSet = presShell->StyleSet(); - - // have to get a parent style context for inherit-like relative - // values (2em, bolder, etc.) - nsRefPtr<nsStyleContext> parentContext; - - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // inherit from the canvas element - parentContext = nsComputedDOMStyle::GetStyleContextForElement( - mCanvasElement, - nullptr, - presShell); - } else { - // otherwise inherit from default (10px sans-serif) - nsRefPtr<css::StyleRule> parentRule; - error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), - document, - getter_AddRefs(parentRule)); - - if (error.Failed()) { - return; - } - - nsTArray< nsCOMPtr<nsIStyleRule> > parentRules; - parentRules.AppendElement(parentRule); - parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); - } - - if (!parentContext) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRefPtr<nsStyleContext> sc = - styleSet->ResolveStyleForRules(parentContext, rules); - if (!sc) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - const nsStyleFont* fontStyle = sc->GetStyleFont(); - - NS_ASSERTION(fontStyle, "Could not obtain font style"); - - nsIAtom* language = sc->GetStyleFont()->mLanguage; - if (!language) { - language = presShell->GetPresContext()->GetLanguageFromCharset(); - } - - // use CSS pixels instead of dev pixels to avoid being affected by page zoom - const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); - // un-zoom the font size to avoid being affected by text-only zoom - // - // Purposely ignore the font size that respects the user's minimum - // font preference (fontStyle->mFont.size) in favor of the computed - // size (fontStyle->mSize). See - // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. - const nscoord fontSize = nsStyleFont::UnZoomText(parentContext->PresContext(), fontStyle->mSize); - - bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || - presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); - - gfxFontStyle style(fontStyle->mFont.style, - fontStyle->mFont.weight, - fontStyle->mFont.stretch, - NSAppUnitsToFloatPixels(fontSize, float(aupcp)), - language, - fontStyle->mFont.sizeAdjust, - fontStyle->mFont.systemFont, - printerFont, - fontStyle->mFont.languageOverride); - - fontStyle->mFont.AddFontFeaturesToStyle(&style); - - CurrentState().fontGroup = - gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, - &style, - presShell->GetPresContext()->GetUserFontSet()); - NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); - - // The font getter is required to be reserialized based on what we - // parsed (including having line-height removed). (Older drafts of - // the spec required font sizes be converted to pixels, but that no - // longer seems to be required.) - declaration->GetValue(eCSSProperty_font, CurrentState().font); -} - -void -CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) -{ - if (ta.EqualsLiteral("start")) - CurrentState().textAlign = TEXT_ALIGN_START; - else if (ta.EqualsLiteral("end")) - CurrentState().textAlign = TEXT_ALIGN_END; - else if (ta.EqualsLiteral("left")) - CurrentState().textAlign = TEXT_ALIGN_LEFT; - else if (ta.EqualsLiteral("right")) - CurrentState().textAlign = TEXT_ALIGN_RIGHT; - else if (ta.EqualsLiteral("center")) - CurrentState().textAlign = TEXT_ALIGN_CENTER; -} - -void -CanvasRenderingContext2D::GetTextAlign(nsAString& ta) -{ - switch (CurrentState().textAlign) - { - case TEXT_ALIGN_START: - ta.AssignLiteral("start"); - break; - case TEXT_ALIGN_END: - ta.AssignLiteral("end"); - break; - case TEXT_ALIGN_LEFT: - ta.AssignLiteral("left"); - break; - case TEXT_ALIGN_RIGHT: - ta.AssignLiteral("right"); - break; - case TEXT_ALIGN_CENTER: - ta.AssignLiteral("center"); - break; - } -} - -void -CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) -{ - if (tb.EqualsLiteral("top")) - CurrentState().textBaseline = TEXT_BASELINE_TOP; - else if (tb.EqualsLiteral("hanging")) - CurrentState().textBaseline = TEXT_BASELINE_HANGING; - else if (tb.EqualsLiteral("middle")) - CurrentState().textBaseline = TEXT_BASELINE_MIDDLE; - else if (tb.EqualsLiteral("alphabetic")) - CurrentState().textBaseline = TEXT_BASELINE_ALPHABETIC; - else if (tb.EqualsLiteral("ideographic")) - CurrentState().textBaseline = TEXT_BASELINE_IDEOGRAPHIC; - else if (tb.EqualsLiteral("bottom")) - CurrentState().textBaseline = TEXT_BASELINE_BOTTOM; -} - -void -CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) -{ - switch (CurrentState().textBaseline) - { - case TEXT_BASELINE_TOP: - tb.AssignLiteral("top"); - break; - case TEXT_BASELINE_HANGING: - tb.AssignLiteral("hanging"); - break; - case TEXT_BASELINE_MIDDLE: - tb.AssignLiteral("middle"); - break; - case TEXT_BASELINE_ALPHABETIC: - tb.AssignLiteral("alphabetic"); - break; - case TEXT_BASELINE_IDEOGRAPHIC: - tb.AssignLiteral("ideographic"); - break; - case TEXT_BASELINE_BOTTOM: - tb.AssignLiteral("bottom"); - break; - } -} - -/* - * Helper function that replaces the whitespace characters in a string - * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, - * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE - * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). - * @param str The string whose whitespace characters to replace. - */ -static inline void -TextReplaceWhitespaceCharacters(nsAutoString& str) -{ - str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", PRUnichar(' ')); -} - -void -CanvasRenderingContext2D::FillText(const nsAString& text, double x, - double y, - const Optional<double>& maxWidth, - ErrorResult& error) -{ - error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nullptr); -} - -void -CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, - double y, - const Optional<double>& maxWidth, - ErrorResult& error) -{ - error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nullptr); -} - -already_AddRefed<nsIDOMTextMetrics> -CanvasRenderingContext2D::MeasureText(const nsAString& rawText, - ErrorResult& error) -{ - float width; - Optional<double> maxWidth; - error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TEXT_DRAW_OPERATION_MEASURE, &width); - if (error.Failed()) { - return NULL; - } - - nsRefPtr<nsIDOMTextMetrics> textMetrics = new TextMetrics(width); - - return textMetrics.forget(); -} - -/** - * Used for nsBidiPresUtils::ProcessText - */ -struct NS_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor -{ - typedef CanvasRenderingContext2D::ContextState ContextState; - - virtual void SetText(const PRUnichar* text, int32_t length, nsBidiDirection direction) - { - mFontgrp->UpdateFontList(); // ensure user font generation is current - mTextRun = mFontgrp->MakeTextRun(text, - length, - mThebes, - mAppUnitsPerDevPixel, - direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); - } - - virtual nscoord GetWidth() - { - gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, - mTextRun->GetLength(), - mDoMeasureBoundingBox ? - gfxFont::TIGHT_INK_EXTENTS : - gfxFont::LOOSE_INK_EXTENTS, - mThebes, - nullptr); - - // this only measures the height; the total width is gotten from the - // the return value of ProcessText. - if (mDoMeasureBoundingBox) { - textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); - mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); - } - - return NSToCoordRound(textRunMetrics.mAdvanceWidth); - } - - virtual void DrawText(nscoord xOffset, nscoord width) - { - gfxPoint point = mPt; - point.x += xOffset; - - // offset is given in terms of left side of string - if (mTextRun->IsRightToLeft()) { - // Bug 581092 - don't use rounded pixel width to advance to - // right-hand end of run, because this will cause different - // glyph positioning for LTR vs RTL drawing of the same - // glyph string on OS X and DWrite where textrun widths may - // involve fractional pixels. - gfxTextRun::Metrics textRunMetrics = - mTextRun->MeasureText(0, - mTextRun->GetLength(), - mDoMeasureBoundingBox ? - gfxFont::TIGHT_INK_EXTENTS : - gfxFont::LOOSE_INK_EXTENTS, - mThebes, - nullptr); - point.x += textRunMetrics.mAdvanceWidth; - // old code was: - // point.x += width * mAppUnitsPerDevPixel; - // TODO: restore this if/when we move to fractional coords - // throughout the text layout process - } - - uint32_t numRuns; - const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); - const uint32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; - const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); - Point baselineOrigin = - Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); - - float advanceSum = 0; - - mCtx->EnsureTarget(); - for (uint32_t c = 0; c < numRuns; c++) { - gfxFont *font = runs[c].mFont; - uint32_t endRun = 0; - if (c + 1 < numRuns) { - endRun = runs[c + 1].mCharacterOffset; - } else { - endRun = mTextRun->GetLength(); - } - - const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); - - RefPtr<ScaledFont> scaledFont = - gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); - - if (!scaledFont) { - // This can occur when something switched DirectWrite off. - return; - } - - GlyphBuffer buffer; - - std::vector<Glyph> glyphBuf; - - for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { - Glyph newGlyph; - if (glyphs[i].IsSimpleGlyph()) { - newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x - advanceSum - - glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; - } else { - newGlyph.mPosition.x = baselineOrigin.x + advanceSum; - } - newGlyph.mPosition.y = baselineOrigin.y; - advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; - glyphBuf.push_back(newGlyph); - continue; - } - - if (!glyphs[i].GetGlyphCount()) { - continue; - } - - gfxTextRun::DetailedGlyph *detailedGlyphs = - mTextRun->GetDetailedGlyphs(i); - - if (glyphs[i].IsMissing()) { - float xpos; - float advance = detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; - if (mTextRun->IsRightToLeft()) { - xpos = baselineOrigin.x - advanceSum - advance; - } else { - xpos = baselineOrigin.x + advanceSum; - } - advanceSum += advance; - - // default-ignorable characters will have zero advance width. - // we don't draw a hexbox for them, just leave them invisible - if (advance > 0) { - // for now, we use gfxFontMissingGlyphs to draw the hexbox; - // some day we should replace this with a direct Azure version - - // get the DrawTarget's transform, so we can apply it to the - // thebes context for gfxFontMissingGlyphs - Matrix matrix = mCtx->mTarget->GetTransform(); - nsRefPtr<gfxContext> thebes; - if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { - // XXX See bug 808288 comment 5 - Bas says: - // This is a little tricky, potentially this could go wrong if - // we fell back to a Cairo context because of for example - // extremely large Canvas size. Cairo content is technically - // -not- supported, but SupportsAzureContent would return true - // as the browser uses D2D content. - // I'm thinking Cairo content will be good enough to do - // DrawMissingGlyph though. - thebes = new gfxContext(mCtx->mTarget); - } else { - nsRefPtr<gfxASurface> drawSurf; - mCtx->GetThebesSurface(getter_AddRefs(drawSurf)); - thebes = new gfxContext(drawSurf); - } - thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, - matrix._22, matrix._31, matrix._32)); - - gfxFloat height = font->GetMetrics().maxAscent; - gfxRect glyphRect(xpos, baselineOrigin.y - height, - advance, height); - gfxFontMissingGlyphs::DrawMissingGlyph(thebes, glyphRect, - detailedGlyphs[0].mGlyphID); - - mCtx->mTarget->SetTransform(matrix); - } - continue; - } - - for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { - newGlyph.mIndex = detailedGlyphs[c].mGlyphID; - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - - advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; - } else { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; - } - newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; - glyphBuf.push_back(newGlyph); - advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; - } - } - - if (!glyphBuf.size()) { - // This may happen for glyph runs for a 0 size font. - continue; - } - - buffer.mGlyphs = &glyphBuf.front(); - buffer.mNumGlyphs = glyphBuf.size(); - - Rect bounds = mCtx->mTarget->GetTransform(). - TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, - mBoundingBox.width, mBoundingBox.height)); - if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL) { - AdjustedTarget(mCtx, &bounds)-> - FillGlyphs(scaledFont, buffer, - CanvasGeneralPattern(). - ForStyle(mCtx, CanvasRenderingContext2D::STYLE_FILL, mCtx->mTarget), - DrawOptions(mState->globalAlpha, mCtx->UsedOperation())); - } else if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) { - RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); - - const ContextState& state = *mState; - AdjustedTarget(mCtx, &bounds)-> - Stroke(path, CanvasGeneralPattern(). - ForStyle(mCtx, CanvasRenderingContext2D::STYLE_STROKE, mCtx->mTarget), - StrokeOptions(state.lineWidth, state.lineJoin, - state.lineCap, state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset), - DrawOptions(state.globalAlpha, mCtx->UsedOperation())); - - } - } - } - - // current text run - nsAutoPtr<gfxTextRun> mTextRun; - - // pointer to a screen reference context used to measure text and such - nsRefPtr<gfxContext> mThebes; - - // Pointer to the draw target we should fill our text to - CanvasRenderingContext2D *mCtx; - - // position of the left side of the string, alphabetic baseline - gfxPoint mPt; - - // current font - gfxFontGroup* mFontgrp; - - // dev pixel conversion factor - uint32_t mAppUnitsPerDevPixel; - - // operation (fill or stroke) - CanvasRenderingContext2D::TextDrawOperation mOp; - - // context state - ContextState *mState; - - // union of bounding boxes of all runs, needed for shadows - gfxRect mBoundingBox; - - // true iff the bounding box should be measured - bool mDoMeasureBoundingBox; -}; - -nsresult -CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, - float aX, - float aY, - const Optional<double>& aMaxWidth, - TextDrawOperation aOp, - float* aWidth) -{ - nsresult rv; - - if (!FloatValidate(aX, aY) || - (aMaxWidth.WasPassed() && !FloatValidate(aMaxWidth.Value()))) - return NS_ERROR_DOM_SYNTAX_ERR; - - // spec isn't clear on what should happen if aMaxWidth <= 0, so - // treat it as an invalid argument - // technically, 0 should be an invalid value as well, but 0 is the default - // arg, and there is no way to tell if the default was used - if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) - return NS_ERROR_INVALID_ARG; - - if (!mCanvasElement && !mDocShell) { - NS_WARNING("Canvas element must be non-null or a docshell must be provided"); - return NS_ERROR_FAILURE; - } - - nsCOMPtr<nsIPresShell> presShell = GetPresShell(); - if (!presShell) - return NS_ERROR_FAILURE; - - nsIDocument* document = presShell->GetDocument(); - - // replace all the whitespace characters with U+0020 SPACE - nsAutoString textToDraw(aRawText); - TextReplaceWhitespaceCharacters(textToDraw); - - // for now, default to ltr if not in doc - bool isRTL = false; - - if (mCanvasElement && mCanvasElement->IsInDoc()) { - // try to find the closest context - nsRefPtr<nsStyleContext> canvasStyle = - nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, - nullptr, - presShell); - if (!canvasStyle) { - return NS_ERROR_FAILURE; - } - - isRTL = canvasStyle->GetStyleVisibility()->mDirection == - NS_STYLE_DIRECTION_RTL; - } else { - isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; - } - - gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); - NS_ASSERTION(currentFontStyle, "font group is null"); - - if (currentFontStyle->GetStyle()->size == 0.0F) { - if (aWidth) { - *aWidth = 0; - } - return NS_OK; - } - - const ContextState &state = CurrentState(); - - // This is only needed to know if we can know the drawing bounding box easily. - bool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow(); - - CanvasBidiProcessor processor; - - GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); - processor.mPt = gfxPoint(aX, aY); - processor.mThebes = - new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); - - // If we don't have a target then we don't have a transform. A target won't - // be needed in the case where we're measuring the text size. This allows - // to avoid creating a target if it's only being used to measure text sizes. - if (mTarget) { - Matrix matrix = mTarget->GetTransform(); - processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); - } - processor.mCtx = this; - processor.mOp = aOp; - processor.mBoundingBox = gfxRect(0, 0, 0, 0); - processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; - processor.mState = &CurrentState(); - processor.mFontgrp = currentFontStyle; - - nscoord totalWidthCoord; - - // calls bidi algo twice since it needs the full text width and the - // bounding boxes before rendering anything - nsBidi bidiEngine; - rv = nsBidiPresUtils::ProcessText(textToDraw.get(), - textToDraw.Length(), - isRTL ? NSBIDI_RTL : NSBIDI_LTR, - presShell->GetPresContext(), - processor, - nsBidiPresUtils::MODE_MEASURE, - nullptr, - 0, - &totalWidthCoord, - &bidiEngine); - if (NS_FAILED(rv)) { - return rv; - } - - float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; - if (aWidth) { - *aWidth = totalWidth; - } - - // if only measuring, don't need to do any more work - if (aOp==TEXT_DRAW_OPERATION_MEASURE) { - return NS_OK; - } - - // offset pt.x based on text align - gfxFloat anchorX; - - if (state.textAlign == TEXT_ALIGN_CENTER) { - anchorX = .5; - } else if (state.textAlign == TEXT_ALIGN_LEFT || - (!isRTL && state.textAlign == TEXT_ALIGN_START) || - (isRTL && state.textAlign == TEXT_ALIGN_END)) { - anchorX = 0; - } else { - anchorX = 1; - } - - processor.mPt.x -= anchorX * totalWidth; - - // offset pt.y based on text baseline - processor.mFontgrp->UpdateFontList(); // ensure user font generation is current - NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); - const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); - - gfxFloat anchorY; - - switch (state.textBaseline) - { - case TEXT_BASELINE_HANGING: - // fall through; best we can do with the information available - case TEXT_BASELINE_TOP: - anchorY = fontMetrics.emAscent; - break; - case TEXT_BASELINE_MIDDLE: - anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; - break; - case TEXT_BASELINE_IDEOGRAPHIC: - // fall through; best we can do with the information available - case TEXT_BASELINE_ALPHABETIC: - anchorY = 0; - break; - case TEXT_BASELINE_BOTTOM: - anchorY = -fontMetrics.emDescent; - break; - default: - MOZ_NOT_REACHED("unexpected TextBaseline"); - } - - processor.mPt.y += anchorY; - - // correct bounding box to get it to be the correct size/position - processor.mBoundingBox.width = totalWidth; - processor.mBoundingBox.MoveBy(processor.mPt); - - processor.mPt.x *= processor.mAppUnitsPerDevPixel; - processor.mPt.y *= processor.mAppUnitsPerDevPixel; - - EnsureTarget(); - Matrix oldTransform = mTarget->GetTransform(); - // if text is over aMaxWidth, then scale the text horizontally such that its - // width is precisely aMaxWidth - if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && - totalWidth > aMaxWidth.Value()) { - Matrix newTransform = oldTransform; - - // Translate so that the anchor point is at 0,0, then scale and then - // translate back. - newTransform.Translate(aX, 0); - newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); - newTransform.Translate(-aX, 0); - /* we do this to avoid an ICE in the android compiler */ - Matrix androidCompilerBug = newTransform; - mTarget->SetTransform(androidCompilerBug); - } - - // save the previous bounding box - gfxRect boundingBox = processor.mBoundingBox; - - // don't ever need to measure the bounding box twice - processor.mDoMeasureBoundingBox = false; - - rv = nsBidiPresUtils::ProcessText(textToDraw.get(), - textToDraw.Length(), - isRTL ? NSBIDI_RTL : NSBIDI_LTR, - presShell->GetPresContext(), - processor, - nsBidiPresUtils::MODE_DRAW, - nullptr, - 0, - nullptr, - &bidiEngine); - - - mTarget->SetTransform(oldTransform); - - if (aOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL && - !doDrawShadow) { - RedrawUser(boundingBox); - return NS_OK; - } - - Redraw(); - return NS_OK; -} - -gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() -{ - // use lazy initilization for the font group since it's rather expensive - if (!CurrentState().fontGroup) { - ErrorResult err; - SetFont(kDefaultFontStyle, err); - if (err.Failed()) { - gfxFontStyle style; - style.size = kDefaultFontSize; - CurrentState().fontGroup = - gfxPlatform::GetPlatform()->CreateFontGroup(kDefaultFontName, - &style, - nullptr); - if (CurrentState().fontGroup) { - CurrentState().font = kDefaultFontStyle; - } else { - NS_ERROR("Default canvas font is invalid"); - } - } - - } - - return CurrentState().fontGroup; -} - -// -// line caps/joins -// - -void -CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) -{ - CapStyle cap; - - if (capstyle.EqualsLiteral("butt")) { - cap = CAP_BUTT; - } else if (capstyle.EqualsLiteral("round")) { - cap = CAP_ROUND; - } else if (capstyle.EqualsLiteral("square")) { - cap = CAP_SQUARE; - } else { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return; - } - - CurrentState().lineCap = cap; -} - -void -CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) -{ - switch (CurrentState().lineCap) { - case CAP_BUTT: - capstyle.AssignLiteral("butt"); - break; - case CAP_ROUND: - capstyle.AssignLiteral("round"); - break; - case CAP_SQUARE: - capstyle.AssignLiteral("square"); - break; - } -} - -void -CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) -{ - JoinStyle j; - - if (joinstyle.EqualsLiteral("round")) { - j = JOIN_ROUND; - } else if (joinstyle.EqualsLiteral("bevel")) { - j = JOIN_BEVEL; - } else if (joinstyle.EqualsLiteral("miter")) { - j = JOIN_MITER_OR_BEVEL; - } else { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return; - } - - CurrentState().lineJoin = j; -} - -void -CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) -{ - switch (CurrentState().lineJoin) { - case JOIN_ROUND: - joinstyle.AssignLiteral("round"); - break; - case JOIN_BEVEL: - joinstyle.AssignLiteral("bevel"); - break; - case JOIN_MITER_OR_BEVEL: - joinstyle.AssignLiteral("miter"); - break; - default: - error.Throw(NS_ERROR_FAILURE); - } -} - -void -CanvasRenderingContext2D::SetMozDash(JSContext* cx, - const JS::Value& mozDash, - ErrorResult& error) -{ - FallibleTArray<Float> dash; - error = JSValToDashArray(cx, mozDash, dash); - if (!error.Failed()) { - ContextState& state = CurrentState(); - state.dash = dash; - if (state.dash.IsEmpty()) { - state.dashOffset = 0; - } - } -} - -JS::Value -CanvasRenderingContext2D::GetMozDash(JSContext* cx, ErrorResult& error) -{ - JS::Value mozDash; - error = DashArrayToJSVal(CurrentState().dash, cx, &mozDash); - return mozDash; -} - -void -CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) -{ - if (!FloatValidate(mozDashOffset)) { - return; - } - - ContextState& state = CurrentState(); +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "base/basictypes.h" +#include "CanvasRenderingContext2D.h" + +#include "nsIDOMXULElement.h" + +#include "prenv.h" + +#include "nsIServiceManager.h" +#include "nsMathUtils.h" + +#include "nsContentUtils.h" + +#include "nsIDocument.h" +#include "nsHTMLCanvasElement.h" +#include "nsSVGEffects.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsIVariant.h" + +#include "nsIInterfaceRequestorUtils.h" +#include "nsIFrame.h" +#include "nsError.h" +#include "nsIScriptError.h" + +#include "nsCSSParser.h" +#include "mozilla/css/StyleRule.h" +#include "mozilla/css/Declaration.h" +#include "nsComputedDOMStyle.h" +#include "nsStyleSet.h" + +#include "nsPrintfCString.h" + +#include "nsReadableUtils.h" + +#include "nsColor.h" +#include "nsGfxCIID.h" +#include "nsIScriptSecurityManager.h" +#include "nsIDocShell.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeNode.h" +#include "nsIXPConnect.h" +#include "nsDisplayList.h" + +#include "nsTArray.h" + +#include "imgIEncoder.h" + +#include "gfxContext.h" +#include "gfxASurface.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxFont.h" +#include "gfxBlur.h" +#include "gfxUtils.h" +#include "gfxFontMissingGlyphs.h" + +#include "nsFrameManager.h" +#include "nsFrameLoader.h" +#include "nsBidi.h" +#include "nsBidiPresUtils.h" +#include "Layers.h" +#include "CanvasUtils.h" +#include "nsIMemoryReporter.h" +#include "nsStyleUtil.h" +#include "CanvasImageCache.h" + +#include <algorithm> + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ipc/DocumentRendererParent.h" +#include "mozilla/ipc/PDocumentRendererParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/unused.h" +#include "nsCCUncollectableMarker.h" +#include "nsWrapperCacheInlines.h" +#include "nsJSUtils.h" +#include "XPCQuickStubs.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsHTMLImageElement.h" +#include "nsHTMLVideoElement.h" +#include "mozilla/dom/CanvasRenderingContext2DBinding.h" + +#ifdef XP_WIN +#include "gfxWindowsPlatform.h" +#endif + +// windows.h (included by chromium code) defines this, in its infinite wisdom +#undef DrawText + +using namespace mozilla; +using namespace mozilla::CanvasUtils; +using namespace mozilla::css; +using namespace mozilla::gfx; +using namespace mozilla::ipc; +using namespace mozilla::layers; + +namespace mgfx = mozilla::gfx; + +#define NS_TEXTMETRICSAZURE_PRIVATE_IID \ + {0x9793f9e7, 0x9dc1, 0x4e9c, {0x81, 0xc8, 0xfc, 0xa7, 0x14, 0xf4, 0x30, 0x79}} + +namespace mozilla { +namespace dom { + +static float kDefaultFontSize = 10.0; +static NS_NAMED_LITERAL_STRING(kDefaultFontName, "sans-serif"); +static NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); + +// Cap sigma to avoid overly large temp surfaces. +const Float SIGMA_MAX = 100; + +/* Memory reporter stuff */ +static nsIMemoryReporter *gCanvasAzureMemoryReporter = nullptr; +static int64_t gCanvasAzureMemoryUsed = 0; + +static int64_t GetCanvasAzureMemoryUsed() { + return gCanvasAzureMemoryUsed; +} + +// This is KIND_OTHER because it's not always clear where in memory the pixels +// of a canvas are stored. Furthermore, this memory will be tracked by the +// underlying surface implementations. See bug 655638 for details. +NS_MEMORY_REPORTER_IMPLEMENT(CanvasAzureMemory, + "canvas-2d-pixel-bytes", + KIND_OTHER, + UNITS_BYTES, + GetCanvasAzureMemoryUsed, + "Memory used by 2D canvases. Each canvas requires (width * height * 4) " + "bytes.") + +class CanvasRadialGradient : public CanvasGradient +{ +public: + CanvasRadialGradient(const Point &aBeginOrigin, Float aBeginRadius, + const Point &aEndOrigin, Float aEndRadius) + : CanvasGradient(RADIAL) + , mCenter1(aBeginOrigin) + , mCenter2(aEndOrigin) + , mRadius1(aBeginRadius) + , mRadius2(aEndRadius) + { + } + + Point mCenter1; + Point mCenter2; + Float mRadius1; + Float mRadius2; +}; + +class CanvasLinearGradient : public CanvasGradient +{ +public: + CanvasLinearGradient(const Point &aBegin, const Point &aEnd) + : CanvasGradient(LINEAR) + , mBegin(aBegin) + , mEnd(aEnd) + { + } + +protected: + friend class CanvasGeneralPattern; + + // Beginning of linear gradient. + Point mBegin; + // End of linear gradient. + Point mEnd; +}; + +// This class is named 'GeneralCanvasPattern' instead of just +// 'GeneralPattern' to keep Windows PGO builds from confusing the +// GeneralPattern class in gfxContext.cpp with this one. + +class CanvasGeneralPattern +{ +public: + typedef CanvasRenderingContext2D::Style Style; + typedef CanvasRenderingContext2D::ContextState ContextState; + + CanvasGeneralPattern() : mPattern(nullptr) {} + ~CanvasGeneralPattern() + { + if (mPattern) { + mPattern->~Pattern(); + } + } + + Pattern& ForStyle(CanvasRenderingContext2D *aCtx, + Style aStyle, + DrawTarget *aRT) + { + // This should only be called once or the mPattern destructor will + // not be executed. + NS_ASSERTION(!mPattern, "ForStyle() should only be called once on CanvasGeneralPattern!"); + + const ContextState &state = aCtx->CurrentState(); + + if (state.StyleIsColor(aStyle)) { + mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); + } else if (state.gradientStyles[aStyle] && + state.gradientStyles[aStyle]->GetType() == CanvasGradient::LINEAR) { + CanvasLinearGradient *gradient = + static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get()); + + mPattern = new (mLinearGradientPattern.addr()) + LinearGradientPattern(gradient->mBegin, gradient->mEnd, + gradient->GetGradientStopsForTarget(aRT)); + } else if (state.gradientStyles[aStyle] && + state.gradientStyles[aStyle]->GetType() == CanvasGradient::RADIAL) { + CanvasRadialGradient *gradient = + static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get()); + + mPattern = new (mRadialGradientPattern.addr()) + RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, + gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT)); + } else if (state.patternStyles[aStyle]) { + if (aCtx->mCanvasElement) { + CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement, + state.patternStyles[aStyle]->mPrincipal, + state.patternStyles[aStyle]->mForceWriteOnly, + state.patternStyles[aStyle]->mCORSUsed); + } + + ExtendMode mode; + if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::NOREPEAT) { + mode = EXTEND_CLAMP; + } else { + mode = EXTEND_REPEAT; + } + mPattern = new (mSurfacePattern.addr()) + SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); + } + + return *mPattern; + } + + union { + AlignedStorage2<ColorPattern> mColorPattern; + AlignedStorage2<LinearGradientPattern> mLinearGradientPattern; + AlignedStorage2<RadialGradientPattern> mRadialGradientPattern; + AlignedStorage2<SurfacePattern> mSurfacePattern; + }; + Pattern *mPattern; +}; + +/* This is an RAII based class that can be used as a drawtarget for + * operations that need a shadow drawn. It will automatically provide a + * temporary target when needed, and if so blend it back with a shadow. + * + * aBounds specifies the bounds of the drawing operation that will be + * drawn to the target, it is given in device space! This function will + * change aBounds to incorporate shadow bounds. If this is NULL the drawing + * operation will be assumed to cover an infinite rect. + */ +class AdjustedTarget +{ +public: + typedef CanvasRenderingContext2D::ContextState ContextState; + + AdjustedTarget(CanvasRenderingContext2D *ctx, + mgfx::Rect *aBounds = nullptr) + : mCtx(nullptr) + { + if (!ctx->NeedToDrawShadow()) { + mTarget = ctx->mTarget; + return; + } + mCtx = ctx; + + const ContextState &state = mCtx->CurrentState(); + + mSigma = state.shadowBlur / 2.0f; + + if (mSigma > SIGMA_MAX) { + mSigma = SIGMA_MAX; + } + + Matrix transform = mCtx->mTarget->GetTransform(); + + mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); + + static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; + int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); + + // We need to enlarge and possibly offset our temporary surface + // so that things outside of the canvas may cast shadows. + mTempRect.Inflate(Margin(blurRadius + NS_MAX<Float>(state.shadowOffset.x, 0), + blurRadius + NS_MAX<Float>(state.shadowOffset.y, 0), + blurRadius + NS_MAX<Float>(-state.shadowOffset.x, 0), + blurRadius + NS_MAX<Float>(-state.shadowOffset.y, 0))); + + if (aBounds) { + // We actually include the bounds of the shadow blur, this makes it + // easier to execute the actual blur on hardware, and shouldn't affect + // the amount of pixels that need to be touched. + aBounds->Inflate(Margin(blurRadius, blurRadius, + blurRadius, blurRadius)); + mTempRect = mTempRect.Intersect(*aBounds); + } + + mTempRect.ScaleRoundOut(1.0f); + + transform._31 -= mTempRect.x; + transform._32 -= mTempRect.y; + + mTarget = + mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), + FORMAT_B8G8R8A8, mSigma); + + if (!mTarget) { + // XXX - Deal with the situation where our temp size is too big to + // fit in a texture. + mTarget = ctx->mTarget; + mCtx = nullptr; + } else { + mTarget->SetTransform(transform); + } + } + + ~AdjustedTarget() + { + if (!mCtx) { + return; + } + + RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); + + mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), + Color::FromABGR(mCtx->CurrentState().shadowColor), + mCtx->CurrentState().shadowOffset, mSigma, + mCtx->CurrentState().op); + } + + DrawTarget* operator->() + { + return mTarget; + } + +private: + RefPtr<DrawTarget> mTarget; + CanvasRenderingContext2D *mCtx; + Float mSigma; + mgfx::Rect mTempRect; +}; + +NS_IMETHODIMP +CanvasGradient::AddColorStop(float offset, const nsAString& colorstr) +{ + if (!FloatValidate(offset) || offset < 0.0 || offset > 1.0) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + nsCSSValue value; + nsCSSParser parser; + if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + nscolor color; + if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + mStops = nullptr; + + GradientStop newStop; + + newStop.offset = offset; + newStop.color = Color::FromABGR(color); + + mRawStops.AppendElement(newStop); + + return NS_OK; +} + +NS_DEFINE_STATIC_IID_ACCESSOR(CanvasGradient, NS_CANVASGRADIENTAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(CanvasGradient) +NS_IMPL_RELEASE(CanvasGradient) + +NS_INTERFACE_MAP_BEGIN(CanvasGradient) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasGradient) + NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasGradient) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_DEFINE_STATIC_IID_ACCESSOR(CanvasPattern, NS_CANVASPATTERNAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(CanvasPattern) +NS_IMPL_RELEASE(CanvasPattern) + +NS_INTERFACE_MAP_BEGIN(CanvasPattern) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::CanvasPattern) + NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CanvasPattern) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/** + ** TextMetrics + **/ +class TextMetrics : public nsIDOMTextMetrics +{ +public: + TextMetrics(float w) : width(w) { } + + virtual ~TextMetrics() { } + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXTMETRICSAZURE_PRIVATE_IID) + + NS_IMETHOD GetWidth(float* w) + { + *w = width; + return NS_OK; + } + + NS_DECL_ISUPPORTS + +private: + float width; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(TextMetrics, NS_TEXTMETRICSAZURE_PRIVATE_IID) + +NS_IMPL_ADDREF(TextMetrics) +NS_IMPL_RELEASE(TextMetrics) + +NS_INTERFACE_MAP_BEGIN(TextMetrics) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::TextMetrics) + NS_INTERFACE_MAP_ENTRY(nsIDOMTextMetrics) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TextMetrics) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +class CanvasRenderingContext2DUserData : public LayerUserData { +public: + CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) + : mContext(aContext) + { + aContext->mUserDatas.AppendElement(this); + } + ~CanvasRenderingContext2DUserData() + { + if (mContext) { + mContext->mUserDatas.RemoveElement(this); + } + } + static void DidTransactionCallback(void* aData) + { + CanvasRenderingContext2DUserData* self = + static_cast<CanvasRenderingContext2DUserData*>(aData); + if (self->mContext) { + self->mContext->MarkContextClean(); + } + } + bool IsForContext(CanvasRenderingContext2D *aContext) + { + return mContext == aContext; + } + void Forget() + { + mContext = nullptr; + } + +private: + CanvasRenderingContext2D *mContext; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) +NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasRenderingContext2D, mCanvasElement) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) + if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { + dom::Element* canvasElement = tmp->mCanvasElement; + if (canvasElement) { + if (canvasElement->IsPurple()) { + canvasElement->RemovePurple(); + } + dom::Element::MarkNodeChildren(canvasElement); + } + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) + return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) + return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/** + ** CanvasRenderingContext2D impl + **/ + + +// Initialize our static variables. +uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; +uint8_t (*CanvasRenderingContext2D::sUnpremultiplyTable)[256] = nullptr; +uint8_t (*CanvasRenderingContext2D::sPremultiplyTable)[256] = nullptr; +DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; + + + +CanvasRenderingContext2D::CanvasRenderingContext2D() + : mZero(false), mOpaque(false), mResetLayer(true) + , mIPC(false) + , mIsEntireFrameInvalid(false) + , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) + , mInvalidateCount(0) +{ + sNumLivingContexts++; + SetIsDOMBinding(); +} + +CanvasRenderingContext2D::~CanvasRenderingContext2D() +{ + Reset(); + // Drop references from all CanvasRenderingContext2DUserData to this context + for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { + mUserDatas[i]->Forget(); + } + sNumLivingContexts--; + if (!sNumLivingContexts) { + delete[] sUnpremultiplyTable; + delete[] sPremultiplyTable; + sUnpremultiplyTable = nullptr; + sPremultiplyTable = nullptr; + NS_IF_RELEASE(sErrorTarget); + } +} + +JSObject* +CanvasRenderingContext2D::WrapObject(JSContext *cx, JSObject *scope, + bool *triedToWrap) +{ + return CanvasRenderingContext2DBinding::Wrap(cx, scope, this, triedToWrap); +} + +bool +CanvasRenderingContext2D::ParseColor(const nsAString& aString, + nscolor* aColor) +{ + nsIDocument* document = mCanvasElement + ? mCanvasElement->OwnerDoc() + : nullptr; + + // Pass the CSS Loader object to the parser, to allow parser error + // reports to include the outer window ID. + nsCSSParser parser(document ? document->CSSLoader() : nullptr); + nsCSSValue value; + if (!parser.ParseColorString(aString, nullptr, 0, value)) { + return false; + } + + if (value.GetUnit() == nsCSSUnit::eCSSUnit_Color) { + // if we already have a color we can just use it directly + *aColor = value.GetColorValue(); + } else { + // otherwise resolve it + nsIPresShell* presShell = GetPresShell(); + nsRefPtr<nsStyleContext> parentContext; + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // Inherit from the canvas element. + parentContext = nsComputedDOMStyle::GetStyleContextForElement( + mCanvasElement, nullptr, presShell); + } + + unused << nsRuleNode::ComputeColor( + value, presShell ? presShell->GetPresContext() : nullptr, parentContext, + *aColor); + } + return true; +} + +nsresult +CanvasRenderingContext2D::Reset() +{ + if (mCanvasElement) { + mCanvasElement->InvalidateCanvas(); + } + + // only do this for non-docshell created contexts, + // since those are the ones that we created a surface for + if (mTarget && IsTargetValid() && !mDocShell) { + gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; + } + + mTarget = nullptr; + + // Since the target changes the backing texture will change, and this will + // no longer be valid. + mThebesSurface = nullptr; + mIsEntireFrameInvalid = false; + mPredictManyRedrawCalls = false; + + return NS_OK; +} + +static void +WarnAboutUnexpectedStyle(nsHTMLCanvasElement* canvasElement) +{ + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, + "Canvas", + canvasElement ? canvasElement->OwnerDoc() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "UnexpectedCanvasVariantStyle"); +} + +void +CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, + Style whichStyle) +{ + MOZ_ASSERT(!str.IsVoid()); + + nscolor color; + if (!ParseColor(str, &color)) { + return; + } + + CurrentState().SetColorStyle(whichStyle, color); +} + +nsISupports* +CanvasRenderingContext2D::GetStyleAsStringOrInterface(nsAString& aStr, + CanvasMultiGetterType& aType, + Style aWhichStyle) +{ + const ContextState &state = CurrentState(); + nsISupports* supports; + if (state.patternStyles[aWhichStyle]) { + aStr.SetIsVoid(true); + supports = state.patternStyles[aWhichStyle]; + aType = CMG_STYLE_PATTERN; + } else if (state.gradientStyles[aWhichStyle]) { + aStr.SetIsVoid(true); + supports = state.gradientStyles[aWhichStyle]; + aType = CMG_STYLE_GRADIENT; + } else { + StyleColorToString(state.colorStyles[aWhichStyle], aStr); + supports = nullptr; + aType = CMG_STYLE_STRING; + } + return supports; +} + +// static +void +CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) +{ + // We can't reuse the normal CSS color stringification code, + // because the spec calls for a different algorithm for canvas. + if (NS_GET_A(aColor) == 255) { + CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", + NS_GET_R(aColor), + NS_GET_G(aColor), + NS_GET_B(aColor)), + aStr); + } else { + CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", + NS_GET_R(aColor), + NS_GET_G(aColor), + NS_GET_B(aColor)), + aStr); + aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); + aStr.Append(')'); + } +} + +nsresult +CanvasRenderingContext2D::Redraw() +{ + if (mIsEntireFrameInvalid) { + return NS_OK; + } + + mIsEntireFrameInvalid = true; + + if (!mCanvasElement) { + NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); + return NS_OK; + } + + if (!mThebesSurface) + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + mThebesSurface->MarkDirty(); + + nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); + + mCanvasElement->InvalidateCanvasContent(nullptr); + + return NS_OK; +} + +void +CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) +{ + ++mInvalidateCount; + + if (mIsEntireFrameInvalid) { + return; + } + + if (mPredictManyRedrawCalls || + mInvalidateCount > kCanvasMaxInvalidateCount) { + Redraw(); + return; + } + + if (!mCanvasElement) { + NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); + return; + } + + if (!mThebesSurface) + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + mThebesSurface->MarkDirty(); + + nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); + + mCanvasElement->InvalidateCanvasContent(&r); +} + +void +CanvasRenderingContext2D::RedrawUser(const gfxRect& r) +{ + if (mIsEntireFrameInvalid) { + ++mInvalidateCount; + return; + } + + mgfx::Rect newr = + mTarget->GetTransform().TransformBounds(ToRect(r)); + Redraw(newr); +} + +void +CanvasRenderingContext2D::EnsureTarget() +{ + if (mTarget) { + return; + } + + // Check that the dimensions are sane + IntSize size(mWidth, mHeight); + if (size.width <= 0xFFFF && size.height <= 0xFFFF && + size.width >= 0 && size.height >= 0) { + SurfaceFormat format = GetSurfaceFormat(); + nsIDocument* ownerDoc = nullptr; + if (mCanvasElement) { + ownerDoc = mCanvasElement->OwnerDoc(); + } + + nsRefPtr<LayerManager> layerManager = nullptr; + + if (ownerDoc) { + layerManager = + nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); + } + + if (layerManager) { + mTarget = layerManager->CreateDrawTarget(size, format); + } else { + mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format); + } + } + + if (mTarget) { + if (gCanvasAzureMemoryReporter == nullptr) { + gCanvasAzureMemoryReporter = new NS_MEMORY_REPORTER_NAME(CanvasAzureMemory); + NS_RegisterMemoryReporter(gCanvasAzureMemoryReporter); + } + + gCanvasAzureMemoryUsed += mWidth * mHeight * 4; + JSContext* context = nsContentUtils::GetCurrentJSContext(); + if (context) { + JS_updateMallocCounter(context, mWidth * mHeight * 4); + } + + mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); + // Force a full layer transaction since we didn't have a layer before + // and now we might need one. + if (mCanvasElement) { + mCanvasElement->InvalidateCanvas(); + } + // Calling Redraw() tells our invalidation machinery that the entire + // canvas is already invalid, which can speed up future drawing. + Redraw(); + } else { + EnsureErrorTarget(); + mTarget = sErrorTarget; + } +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) +{ + ClearTarget(); + + // Zero sized surfaces cause issues, so just go with 1x1. + if (height == 0 || width == 0) { + mZero = true; + mWidth = 1; + mHeight = 1; + } else { + mZero = false; + mWidth = width; + mHeight = height; + } + + return NS_OK; +} + +void +CanvasRenderingContext2D::ClearTarget() +{ + Reset(); + + mResetLayer = true; + + // set up the initial canvas defaults + mStyleStack.Clear(); + mPathBuilder = nullptr; + mPath = nullptr; + mDSPathBuilder = nullptr; + + ContextState *state = mStyleStack.AppendElement(); + state->globalAlpha = 1.0; + + state->colorStyles[STYLE_FILL] = NS_RGB(0,0,0); + state->colorStyles[STYLE_STROKE] = NS_RGB(0,0,0); + state->shadowColor = NS_RGBA(0,0,0,0); +} + +NS_IMETHODIMP +CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, + gfxASurface *surface, + int32_t width, + int32_t height) +{ + mDocShell = shell; + mThebesSurface = surface; + + SetDimensions(width, height); + mTarget = gfxPlatform::GetPlatform()-> + CreateDrawTargetForSurface(surface, IntSize(width, height)); + if (!mTarget) { + EnsureErrorTarget(); + mTarget = sErrorTarget; + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) +{ + if (isOpaque != mOpaque) { + mOpaque = isOpaque; + ClearTarget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::SetIsIPC(bool isIPC) +{ + if (isIPC != mIPC) { + mIPC = isIPC; + ClearTarget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, uint32_t aFlags) +{ + nsresult rv = NS_OK; + + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr<gfxASurface> surface; + + if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { + return NS_ERROR_FAILURE; + } + + nsRefPtr<gfxPattern> pat = new gfxPattern(surface); + + pat->SetFilter(aFilter); + pat->SetExtend(gfxPattern::EXTEND_PAD); + + gfxContext::GraphicsOperator op = ctx->CurrentOperator(); + if (mOpaque) + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + + // XXX I don't want to use PixelSnapped here, but layout doesn't guarantee + // pixel alignment for this stuff! + ctx->NewPath(); + ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat); + ctx->Fill(); + + if (mOpaque) + ctx->SetOperator(op); + + if (!(aFlags & RenderFlagPremultAlpha)) { + nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface(); + nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface(); + NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!"); + + gfxUtils::UnpremultiplyImageSurface(gis); + } + + return rv; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::GetInputStream(const char *aMimeType, + const PRUnichar *aEncoderOptions, + nsIInputStream **aStream) +{ + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr<gfxASurface> surface; + + if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; + nsAutoArrayPtr<char> conid(new (std::nothrow) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); + + if (!conid) { + return NS_ERROR_OUT_OF_MEMORY; + } + + strcpy(conid, encoderPrefix); + strcat(conid, aMimeType); + + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid); + if (!encoder) { + return NS_ERROR_FAILURE; + } + + nsAutoArrayPtr<uint8_t> imageBuffer(new (std::nothrow) uint8_t[mWidth * mHeight * 4]); + if (!imageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsRefPtr<gfxImageSurface> imgsurf = + new gfxImageSurface(imageBuffer.get(), + gfxIntSize(mWidth, mHeight), + mWidth * 4, + gfxASurface::ImageFormatARGB32); + + if (!imgsurf || imgsurf->CairoStatus()) { + return NS_ERROR_FAILURE; + } + + nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf); + + if (!ctx || ctx->HasError()) { + return NS_ERROR_FAILURE; + } + + ctx->SetOperator(gfxContext::OPERATOR_SOURCE); + ctx->SetSource(surface, gfxPoint(0, 0)); + ctx->Paint(); + + rv = encoder->InitFromData(imageBuffer.get(), + mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4, + imgIEncoder::INPUT_FORMAT_HOSTARGB, + nsDependentString(aEncoderOptions)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(encoder, aStream); +} + +SurfaceFormat +CanvasRenderingContext2D::GetSurfaceFormat() const +{ + return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8; +} + +// +// state +// + +void +CanvasRenderingContext2D::Save() +{ + EnsureTarget(); + mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); + mStyleStack.SetCapacity(mStyleStack.Length() + 1); + mStyleStack.AppendElement(CurrentState()); +} + +void +CanvasRenderingContext2D::Restore() +{ + if (mStyleStack.Length() - 1 == 0) + return; + + TransformWillUpdate(); + + for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { + mTarget->PopClip(); + } + + mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); + + mTarget->SetTransform(CurrentState().transform); +} + +// +// transformations +// + +void +CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) +{ + if (!FloatValidate(x,y)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newMatrix = mTarget->GetTransform(); + mTarget->SetTransform(newMatrix.Scale(x, y)); +} + +void +CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) +{ + if (!FloatValidate(angle)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + + Matrix rotation = Matrix::Rotation(angle); + mTarget->SetTransform(rotation * mTarget->GetTransform()); +} + +void +CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) +{ + if (!FloatValidate(x,y)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newMatrix = mTarget->GetTransform(); + mTarget->SetTransform(newMatrix.Translate(x, y)); +} + +void +CanvasRenderingContext2D::Transform(double m11, double m12, double m21, + double m22, double dx, double dy, + ErrorResult& error) +{ + if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix matrix(m11, m12, m21, m22, dx, dy); + mTarget->SetTransform(matrix * mTarget->GetTransform()); +} + +void +CanvasRenderingContext2D::SetTransform(double m11, double m12, + double m21, double m22, + double dx, double dy, + ErrorResult& error) +{ + if (!FloatValidate(m11,m12,m21,m22,dx,dy)) { + return; + } + + TransformWillUpdate(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix matrix(m11, m12, m21, m22, dx, dy); + mTarget->SetTransform(matrix); +} + +JSObject* +MatrixToJSObject(JSContext* cx, const Matrix& matrix, ErrorResult& error) +{ + jsval elts[] = { + DOUBLE_TO_JSVAL(matrix._11), DOUBLE_TO_JSVAL(matrix._12), + DOUBLE_TO_JSVAL(matrix._21), DOUBLE_TO_JSVAL(matrix._22), + DOUBLE_TO_JSVAL(matrix._31), DOUBLE_TO_JSVAL(matrix._32) + }; + + // XXX Should we enter GetWrapper()'s compartment? + JSObject* obj = JS_NewArrayObject(cx, 6, elts); + if (!obj) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + } + return obj; +} + +bool +ObjectToMatrix(JSContext* cx, JSObject& obj, Matrix& matrix, ErrorResult& error) +{ + uint32_t length; + if (!JS_GetArrayLength(cx, &obj, &length) || length != 6) { + // Not an array-like thing or wrong size + error.Throw(NS_ERROR_INVALID_ARG); + return false; + } + + Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, + &matrix._31, &matrix._32 }; + for (uint32_t i = 0; i < 6; ++i) { + jsval elt; + double d; + if (!JS_GetElement(cx, &obj, i, &elt)) { + error.Throw(NS_ERROR_FAILURE); + return false; + } + if (!CoerceDouble(elt, &d)) { + error.Throw(NS_ERROR_INVALID_ARG); + return false; + } + if (!FloatValidate(d)) { + // This is weird, but it's the behavior of SetTransform() + return false; + } + *elts[i] = Float(d); + } + return true; +} + +void +CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, + JSObject& currentTransform, + ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newCTM; + if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { + mTarget->SetTransform(newCTM); + } +} + +JSObject* +CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, + ErrorResult& error) const +{ + return MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), error); +} + +void +CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, + JSObject& currentTransform, + ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + Matrix newCTMInverse; + if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + if (newCTMInverse.Invert()) { + mTarget->SetTransform(newCTMInverse); + } + } +} + +JSObject* +CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, + ErrorResult& error) const +{ + if (!mTarget) { + return MatrixToJSObject(cx, Matrix(), error); + } + + Matrix ctm = mTarget->GetTransform(); + + if (!ctm.Invert()) { + double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); + ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); + } + + return MatrixToJSObject(cx, ctm, error); +} + +// +// colors +// + +void +CanvasRenderingContext2D::SetStyleFromJSValue(JSContext* cx, + JS::Value& value, + Style whichStyle) +{ + if (value.isString()) { + nsDependentJSString strokeStyle; + if (strokeStyle.init(cx, value.toString())) { + SetStyleFromString(strokeStyle, whichStyle); + } + return; + } + + if (value.isObject()) { + nsCOMPtr<nsISupports> holder; + + CanvasGradient* gradient; + nsresult rv = xpc_qsUnwrapArg<CanvasGradient>(cx, value, &gradient, + static_cast<nsISupports**>(getter_AddRefs(holder)), + &value); + if (NS_SUCCEEDED(rv)) { + SetStyleFromGradient(gradient, whichStyle); + return; + } + + CanvasPattern* pattern; + rv = xpc_qsUnwrapArg<CanvasPattern>(cx, value, &pattern, + static_cast<nsISupports**>(getter_AddRefs(holder)), + &value); + if (NS_SUCCEEDED(rv)) { + SetStyleFromPattern(pattern, whichStyle); + return; + } + } + + WarnAboutUnexpectedStyle(mCanvasElement); +} + +static JS::Value +WrapStyle(JSContext* cx, JSObject* obj, + CanvasRenderingContext2D::CanvasMultiGetterType type, + nsAString& str, nsISupports* supports, ErrorResult& error) +{ + JS::Value v; + bool ok; + switch (type) { + case CanvasRenderingContext2D::CMG_STYLE_STRING: + { + ok = xpc::StringToJsval(cx, str, &v); + break; + } + case CanvasRenderingContext2D::CMG_STYLE_PATTERN: + case CanvasRenderingContext2D::CMG_STYLE_GRADIENT: + { + ok = dom::WrapObject(cx, obj, supports, &v); + break; + } + default: + MOZ_NOT_REACHED("unexpected CanvasMultiGetterType"); + } + if (!ok) { + error.Throw(NS_ERROR_FAILURE); + } + return v; +} + + +JS::Value +CanvasRenderingContext2D::GetStrokeStyle(JSContext* cx, + ErrorResult& error) +{ + nsString str; + CanvasMultiGetterType t; + nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_STROKE); + return WrapStyle(cx, GetWrapper(), t, str, supports, error); +} + +JS::Value +CanvasRenderingContext2D::GetFillStyle(JSContext* cx, + ErrorResult& error) +{ + nsString str; + CanvasMultiGetterType t; + nsISupports* supports = GetStyleAsStringOrInterface(str, t, STYLE_FILL); + return WrapStyle(cx, GetWrapper(), t, str, supports, error); +} + +void +CanvasRenderingContext2D::SetFillRule(const nsAString& aString) +{ + FillRule rule; + + if (aString.EqualsLiteral("evenodd")) + rule = FILL_EVEN_ODD; + else if (aString.EqualsLiteral("nonzero")) + rule = FILL_WINDING; + else + return; + + CurrentState().fillRule = rule; +} + +void +CanvasRenderingContext2D::GetFillRule(nsAString& aString) +{ + switch (CurrentState().fillRule) { + case FILL_WINDING: + aString.AssignLiteral("nonzero"); break; + case FILL_EVEN_ODD: + aString.AssignLiteral("evenodd"); break; + } +} +// +// gradients and patterns +// +already_AddRefed<nsIDOMCanvasGradient> +CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1, + ErrorResult& aError) +{ + if (!FloatValidate(x0,y0,x1,y1)) { + aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsRefPtr<nsIDOMCanvasGradient> grad = + new CanvasLinearGradient(Point(x0, y0), Point(x1, y1)); + + return grad.forget(); +} + +already_AddRefed<nsIDOMCanvasGradient> +CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, + double x1, double y1, double r1, + ErrorResult& aError) +{ + if (!FloatValidate(x0,y0,r0,x1,y1,r1)) { + aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + if (r0 < 0.0 || r1 < 0.0) { + aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return nullptr; + } + + nsRefPtr<nsIDOMCanvasGradient> grad = + new CanvasRadialGradient(Point(x0, y0), r0, Point(x1, y1), r1); + + return grad.forget(); +} + +already_AddRefed<nsIDOMCanvasPattern> +CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, + const nsAString& repeat, + ErrorResult& error) +{ + CanvasPattern::RepeatMode repeatMode = + CanvasPattern::NOREPEAT; + + if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { + repeatMode = CanvasPattern::REPEAT; + } else if (repeat.EqualsLiteral("repeat-x")) { + repeatMode = CanvasPattern::REPEATX; + } else if (repeat.EqualsLiteral("repeat-y")) { + repeatMode = CanvasPattern::REPEATY; + } else if (repeat.EqualsLiteral("no-repeat")) { + repeatMode = CanvasPattern::NOREPEAT; + } else { + error.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return NULL; + } + + Element* htmlElement; + if (element.IsHTMLCanvasElement()) { + nsHTMLCanvasElement* canvas = element.GetAsHTMLCanvasElement(); + htmlElement = canvas; + + nsIntSize size = canvas->GetSize(); + if (size.width == 0 || size.height == 0) { + error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return NULL; + } + + // Special case for Canvas, which could be an Azure canvas! + nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); + if (srcCanvas) { + // This might not be an Azure canvas! + RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); + + nsRefPtr<CanvasPattern> pat = + new CanvasPattern(srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); + + return pat.forget(); + } + } else if (element.IsHTMLImageElement()) { + htmlElement = element.GetAsHTMLImageElement(); + } else { + htmlElement = element.GetAsHTMLVideoElement(); + } + + // The canvas spec says that createPattern should use the first frame + // of animated images + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(htmlElement, + nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE); + + if (!res.mSurface) { + error.Throw(NS_ERROR_NOT_AVAILABLE); + return NULL; + } + + // Ignore nullptr cairo surfaces! See bug 666312. + if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) { + return NULL; + } + + EnsureTarget(); + RefPtr<SourceSurface> srcSurf = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); + + nsRefPtr<CanvasPattern> pat = + new CanvasPattern(srcSurf, repeatMode, res.mPrincipal, + res.mIsWriteOnly, res.mCORSUsed); + + return pat.forget(); +} + +// +// shadows +// +void +CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) +{ + nscolor color; + if (!ParseColor(shadowColor, &color)) { + return; + } + + CurrentState().shadowColor = color; +} + +// +// rects +// + +void +CanvasRenderingContext2D::ClearRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h) || !mTarget) { + return; + } + + mTarget->ClearRect(mgfx::Rect(x, y, w, h)); + + RedrawUser(gfxRect(x, y, w, h)); +} + +void +CanvasRenderingContext2D::FillRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h)) { + return; + } + + const ContextState &state = CurrentState(); + + if (state.patternStyles[STYLE_FILL]) { + CanvasPattern::RepeatMode repeat = + state.patternStyles[STYLE_FILL]->mRepeat; + // In the FillRect case repeat modes are easy to deal with. + bool limitx = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATY; + bool limity = repeat == CanvasPattern::NOREPEAT || repeat == CanvasPattern::REPEATX; + + IntSize patternSize = + state.patternStyles[STYLE_FILL]->mSurface->GetSize(); + + // We always need to execute painting for non-over operators, even if + // we end up with w/h = 0. + if (limitx) { + if (x < 0) { + w += x; + if (w < 0) { + w = 0; + } + + x = 0; + } + if (x + w > patternSize.width) { + w = patternSize.width - x; + if (w < 0) { + w = 0; + } + } + } + if (limity) { + if (y < 0) { + h += y; + if (h < 0) { + h = 0; + } + + y = 0; + } + if (y + h > patternSize.height) { + h = patternSize.height - y; + if (h < 0) { + h = 0; + } + } + } + } + + mgfx::Rect bounds; + + EnsureTarget(); + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(x, y, w, h); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + FillRect(mgfx::Rect(x, y, w, h), + CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), + DrawOptions(state.globalAlpha, UsedOperation())); + + RedrawUser(gfxRect(x, y, w, h)); +} + +void +CanvasRenderingContext2D::StrokeRect(double x, double y, double w, + double h) +{ + if (!FloatValidate(x,y,w,h)) { + return; + } + + const ContextState &state = CurrentState(); + + mgfx::Rect bounds; + + if (!w && !h) { + return; + } + + EnsureTarget(); + if (!IsTargetValid()) { + return; + } + + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, + w + state.lineWidth, h + state.lineWidth); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + if (!h) { + CapStyle cap = CAP_BUTT; + if (state.lineJoin == JOIN_ROUND) { + cap = CAP_ROUND; + } + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeLine(Point(x, y), Point(x + w, y), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + cap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + return; + } + + if (!w) { + CapStyle cap = CAP_BUTT; + if (state.lineJoin == JOIN_ROUND) { + cap = CAP_ROUND; + } + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeLine(Point(x, y), Point(x, y + h), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + cap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + return; + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + StrokeRect(mgfx::Rect(x, y, w, h), + CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, UsedOperation())); + + Redraw(); +} + +// +// path bits +// + +void +CanvasRenderingContext2D::BeginPath() +{ + mPath = nullptr; + mPathBuilder = nullptr; + mDSPathBuilder = nullptr; + mPathTransformWillUpdate = false; +} + +void +CanvasRenderingContext2D::Fill() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + mgfx::Rect bounds; + + if (NeedToDrawShadow()) { + bounds = mPath->GetBounds(mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Fill(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_FILL, mTarget), + DrawOptions(CurrentState().globalAlpha, UsedOperation())); + + Redraw(); +} + +void +CanvasRenderingContext2D::Stroke() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + const ContextState &state = CurrentState(); + + StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), state.dash.Elements(), + state.dashOffset); + + mgfx::Rect bounds; + if (NeedToDrawShadow()) { + bounds = + mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + Stroke(mPath, CanvasGeneralPattern().ForStyle(this, STYLE_STROKE, mTarget), + strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); + + Redraw(); +} + +void +CanvasRenderingContext2D::Clip() +{ + EnsureUserSpacePath(); + + if (!mPath) { + return; + } + + mTarget->PushClip(mPath); + CurrentState().clipsPushed.push_back(mPath); +} + +void +CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, + double y2, double radius, + ErrorResult& error) +{ + if (!FloatValidate(x1, y1, x2, y2, radius)) { + return; + } + + if (radius < 0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + EnsureWritablePath(); + + // Current point in user space! + Point p0; + if (mPathBuilder) { + p0 = mPathBuilder->CurrentPoint(); + } else { + Matrix invTransform = mTarget->GetTransform(); + if (!invTransform.Invert()) { + return; + } + + p0 = invTransform * mDSPathBuilder->CurrentPoint(); + } + + Point p1(x1, y1); + Point p2(x2, y2); + + // Execute these calculations in double precision to avoid cumulative + // rounding errors. + double dir, a2, b2, c2, cosx, sinx, d, anx, any, + bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; + bool anticlockwise; + + if (p0 == p1 || p1 == p2 || radius == 0) { + LineTo(p1.x, p1.y); + return; + } + + // Check for colinearity + dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); + if (dir == 0) { + LineTo(p1.x, p1.y); + return; + } + + + // XXX - Math for this code was already available from the non-azure code + // and would be well tested. Perhaps converting to bezier directly might + // be more efficient longer run. + a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); + b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); + c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); + cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); + + sinx = sqrt(1 - cosx*cosx); + d = radius / ((1 - cosx) / sinx); + + anx = (x1-p0.x) / sqrt(a2); + any = (y1-p0.y) / sqrt(a2); + bnx = (x1-x2) / sqrt(b2); + bny = (y1-y2) / sqrt(b2); + x3 = x1 - anx*d; + y3 = y1 - any*d; + x4 = x1 - bnx*d; + y4 = y1 - bny*d; + anticlockwise = (dir < 0); + cx = x3 + any*radius*(anticlockwise ? 1 : -1); + cy = y3 - anx*radius*(anticlockwise ? 1 : -1); + angle0 = atan2((y3-cy), (x3-cx)); + angle1 = atan2((y4-cy), (x4-cx)); + + + LineTo(x3, y3); + + Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); +} + +void +CanvasRenderingContext2D::Arc(double x, double y, double r, + double startAngle, double endAngle, + bool anticlockwise, ErrorResult& error) +{ + if (!FloatValidate(x, y, r, startAngle, endAngle)) { + return; + } + + if (r < 0.0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + EnsureWritablePath(); + + ArcToBezier(this, Point(x, y), r, startAngle, endAngle, anticlockwise); +} + +void +CanvasRenderingContext2D::Rect(double x, double y, double w, double h) +{ + if (!FloatValidate(x, y, w, h)) { + return; + } + + EnsureWritablePath(); + + if (mPathBuilder) { + mPathBuilder->MoveTo(Point(x, y)); + mPathBuilder->LineTo(Point(x + w, y)); + mPathBuilder->LineTo(Point(x + w, y + h)); + mPathBuilder->LineTo(Point(x, y + h)); + mPathBuilder->Close(); + } else { + mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); + mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); + mDSPathBuilder->Close(); + } +} + +void +CanvasRenderingContext2D::EnsureWritablePath() +{ + if (mDSPathBuilder) { + return; + } + + FillRule fillRule = CurrentState().fillRule; + + if (mPathBuilder) { + if (mPathTransformWillUpdate) { + mPath = mPathBuilder->Finish(); + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPath = nullptr; + mPathBuilder = nullptr; + mPathTransformWillUpdate = false; + } + return; + } + + EnsureTarget(); + if (!mPath) { + NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); + mPathBuilder = mTarget->CreatePathBuilder(fillRule); + } else if (!mPathTransformWillUpdate) { + mPathBuilder = mPath->CopyToBuilder(fillRule); + } else { + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPathTransformWillUpdate = false; + } +} + +void +CanvasRenderingContext2D::EnsureUserSpacePath(bool aCommitTransform /* = true */) +{ + FillRule fillRule = CurrentState().fillRule; + + if (!mPath && !mPathBuilder && !mDSPathBuilder) { + EnsureTarget(); + mPathBuilder = mTarget->CreatePathBuilder(fillRule); + } + + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (aCommitTransform && + mPath && + mPathTransformWillUpdate) { + mDSPathBuilder = + mPath->TransformedCopyToBuilder(mPathToDS, fillRule); + mPath = nullptr; + mPathTransformWillUpdate = false; + } + + if (mDSPathBuilder) { + RefPtr<Path> dsPath; + dsPath = mDSPathBuilder->Finish(); + mDSPathBuilder = nullptr; + + Matrix inverse = mTarget->GetTransform(); + if (!inverse.Invert()) { + NS_WARNING("Could not invert transform"); + return; + } + + mPathBuilder = + dsPath->TransformedCopyToBuilder(inverse, fillRule); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath && mPath->GetFillRule() != fillRule) { + mPathBuilder = mPath->CopyToBuilder(fillRule); + mPath = mPathBuilder->Finish(); + } + + NS_ASSERTION(mPath, "mPath should exist"); +} + +void +CanvasRenderingContext2D::TransformWillUpdate() +{ + EnsureTarget(); + + // Store the matrix that would transform the current path to device + // space. + if (mPath || mPathBuilder) { + if (!mPathTransformWillUpdate) { + // If the transform has already been updated, but a device space builder + // has not been created yet mPathToDS contains the right transform to + // transform the current mPath into device space. + // We should leave it alone. + mPathToDS = mTarget->GetTransform(); + } + mPathTransformWillUpdate = true; + } +} + +// +// text +// + +/** + * Helper function for SetFont that creates a style rule for the given font. + * @param aFont The CSS font string + * @param aNode The canvas element + * @param aResult Pointer in which to place the new style rule. + * @remark Assumes all pointer arguments are non-null. + */ +static nsresult +CreateFontStyleRule(const nsAString& aFont, + nsINode* aNode, + StyleRule** aResult) +{ + nsRefPtr<StyleRule> rule; + bool changed; + + nsIPrincipal* principal = aNode->NodePrincipal(); + nsIDocument* document = aNode->OwnerDoc(); + + nsIURI* docURL = document->GetDocumentURI(); + nsIURI* baseURL = document->GetDocBaseURI(); + + // Pass the CSS Loader object to the parser, to allow parser error reports + // to include the outer window ID. + nsCSSParser parser(document->CSSLoader()); + + nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, + principal, getter_AddRefs(rule)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, + principal, rule->GetDeclaration(), &changed, + false); + if (NS_FAILED(rv)) + return rv; + + rv = parser.ParseProperty(eCSSProperty_line_height, + NS_LITERAL_STRING("normal"), docURL, baseURL, + principal, rule->GetDeclaration(), &changed, + false); + if (NS_FAILED(rv)) { + return rv; + } + + rule->RuleMatched(); + + rule.forget(aResult); + return NS_OK; +} + +void +CanvasRenderingContext2D::SetFont(const nsAString& font, + ErrorResult& error) +{ + /* + * If font is defined with relative units (e.g. ems) and the parent + * style context changes in between calls, setting the font to the + * same value as previous could result in a different computed value, + * so we cannot have the optimization where we check if the new font + * string is equal to the old one. + */ + + if (!mCanvasElement && !mDocShell) { + NS_WARNING("Canvas element must be non-null or a docshell must be provided"); + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsIPresShell* presShell = GetPresShell(); + if (!presShell) { + error.Throw(NS_ERROR_FAILURE); + return; + } + nsIDocument* document = presShell->GetDocument(); + + nsRefPtr<css::StyleRule> rule; + error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); + + if (error.Failed()) { + return; + } + + css::Declaration *declaration = rule->GetDeclaration(); + // The easiest way to see whether we got a syntax error or whether + // we got 'inherit' or 'initial' is to look at font-size-adjust, + // which the shorthand resets to either 'none' or + // '-moz-system-font'. + // We know the declaration is not !important, so we can use + // GetNormalBlock(). + const nsCSSValue *fsaVal = + declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); + if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && + fsaVal->GetUnit() != eCSSUnit_System_Font)) { + // We got an all-property value or a syntax error. The spec says + // this value must be ignored. + return; + } + + nsTArray< nsCOMPtr<nsIStyleRule> > rules; + rules.AppendElement(rule); + + nsStyleSet* styleSet = presShell->StyleSet(); + + // have to get a parent style context for inherit-like relative + // values (2em, bolder, etc.) + nsRefPtr<nsStyleContext> parentContext; + + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // inherit from the canvas element + parentContext = nsComputedDOMStyle::GetStyleContextForElement( + mCanvasElement, + nullptr, + presShell); + } else { + // otherwise inherit from default (10px sans-serif) + nsRefPtr<css::StyleRule> parentRule; + error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), + document, + getter_AddRefs(parentRule)); + + if (error.Failed()) { + return; + } + + nsTArray< nsCOMPtr<nsIStyleRule> > parentRules; + parentRules.AppendElement(parentRule); + parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); + } + + if (!parentContext) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRefPtr<nsStyleContext> sc = + styleSet->ResolveStyleForRules(parentContext, rules); + if (!sc) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + const nsStyleFont* fontStyle = sc->GetStyleFont(); + + NS_ASSERTION(fontStyle, "Could not obtain font style"); + + nsIAtom* language = sc->GetStyleFont()->mLanguage; + if (!language) { + language = presShell->GetPresContext()->GetLanguageFromCharset(); + } + + // use CSS pixels instead of dev pixels to avoid being affected by page zoom + const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); + // un-zoom the font size to avoid being affected by text-only zoom + // + // Purposely ignore the font size that respects the user's minimum + // font preference (fontStyle->mFont.size) in favor of the computed + // size (fontStyle->mSize). See + // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. + const nscoord fontSize = nsStyleFont::UnZoomText(parentContext->PresContext(), fontStyle->mSize); + + bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || + presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); + + gfxFontStyle style(fontStyle->mFont.style, + fontStyle->mFont.weight, + fontStyle->mFont.stretch, + NSAppUnitsToFloatPixels(fontSize, float(aupcp)), + language, + fontStyle->mFont.sizeAdjust, + fontStyle->mFont.systemFont, + printerFont, + fontStyle->mFont.languageOverride); + + fontStyle->mFont.AddFontFeaturesToStyle(&style); + + CurrentState().fontGroup = + gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, + &style, + presShell->GetPresContext()->GetUserFontSet()); + NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); + + // The font getter is required to be reserialized based on what we + // parsed (including having line-height removed). (Older drafts of + // the spec required font sizes be converted to pixels, but that no + // longer seems to be required.) + declaration->GetValue(eCSSProperty_font, CurrentState().font); +} + +void +CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) +{ + if (ta.EqualsLiteral("start")) + CurrentState().textAlign = TEXT_ALIGN_START; + else if (ta.EqualsLiteral("end")) + CurrentState().textAlign = TEXT_ALIGN_END; + else if (ta.EqualsLiteral("left")) + CurrentState().textAlign = TEXT_ALIGN_LEFT; + else if (ta.EqualsLiteral("right")) + CurrentState().textAlign = TEXT_ALIGN_RIGHT; + else if (ta.EqualsLiteral("center")) + CurrentState().textAlign = TEXT_ALIGN_CENTER; +} + +void +CanvasRenderingContext2D::GetTextAlign(nsAString& ta) +{ + switch (CurrentState().textAlign) + { + case TEXT_ALIGN_START: + ta.AssignLiteral("start"); + break; + case TEXT_ALIGN_END: + ta.AssignLiteral("end"); + break; + case TEXT_ALIGN_LEFT: + ta.AssignLiteral("left"); + break; + case TEXT_ALIGN_RIGHT: + ta.AssignLiteral("right"); + break; + case TEXT_ALIGN_CENTER: + ta.AssignLiteral("center"); + break; + } +} + +void +CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) +{ + if (tb.EqualsLiteral("top")) + CurrentState().textBaseline = TEXT_BASELINE_TOP; + else if (tb.EqualsLiteral("hanging")) + CurrentState().textBaseline = TEXT_BASELINE_HANGING; + else if (tb.EqualsLiteral("middle")) + CurrentState().textBaseline = TEXT_BASELINE_MIDDLE; + else if (tb.EqualsLiteral("alphabetic")) + CurrentState().textBaseline = TEXT_BASELINE_ALPHABETIC; + else if (tb.EqualsLiteral("ideographic")) + CurrentState().textBaseline = TEXT_BASELINE_IDEOGRAPHIC; + else if (tb.EqualsLiteral("bottom")) + CurrentState().textBaseline = TEXT_BASELINE_BOTTOM; +} + +void +CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) +{ + switch (CurrentState().textBaseline) + { + case TEXT_BASELINE_TOP: + tb.AssignLiteral("top"); + break; + case TEXT_BASELINE_HANGING: + tb.AssignLiteral("hanging"); + break; + case TEXT_BASELINE_MIDDLE: + tb.AssignLiteral("middle"); + break; + case TEXT_BASELINE_ALPHABETIC: + tb.AssignLiteral("alphabetic"); + break; + case TEXT_BASELINE_IDEOGRAPHIC: + tb.AssignLiteral("ideographic"); + break; + case TEXT_BASELINE_BOTTOM: + tb.AssignLiteral("bottom"); + break; + } +} + +/* + * Helper function that replaces the whitespace characters in a string + * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, + * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE + * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). + * @param str The string whose whitespace characters to replace. + */ +static inline void +TextReplaceWhitespaceCharacters(nsAutoString& str) +{ + str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", PRUnichar(' ')); +} + +void +CanvasRenderingContext2D::FillText(const nsAString& text, double x, + double y, + const Optional<double>& maxWidth, + ErrorResult& error) +{ + error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nullptr); +} + +void +CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, + double y, + const Optional<double>& maxWidth, + ErrorResult& error) +{ + error = DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nullptr); +} + +already_AddRefed<nsIDOMTextMetrics> +CanvasRenderingContext2D::MeasureText(const nsAString& rawText, + ErrorResult& error) +{ + float width; + Optional<double> maxWidth; + error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TEXT_DRAW_OPERATION_MEASURE, &width); + if (error.Failed()) { + return NULL; + } + + nsRefPtr<nsIDOMTextMetrics> textMetrics = new TextMetrics(width); + + return textMetrics.forget(); +} + +/** + * Used for nsBidiPresUtils::ProcessText + */ +struct NS_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor +{ + typedef CanvasRenderingContext2D::ContextState ContextState; + + virtual void SetText(const PRUnichar* text, int32_t length, nsBidiDirection direction) + { + mFontgrp->UpdateFontList(); // ensure user font generation is current + mTextRun = mFontgrp->MakeTextRun(text, + length, + mThebes, + mAppUnitsPerDevPixel, + direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); + } + + virtual nscoord GetWidth() + { + gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, + mTextRun->GetLength(), + mDoMeasureBoundingBox ? + gfxFont::TIGHT_INK_EXTENTS : + gfxFont::LOOSE_INK_EXTENTS, + mThebes, + nullptr); + + // this only measures the height; the total width is gotten from the + // the return value of ProcessText. + if (mDoMeasureBoundingBox) { + textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); + mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); + } + + return NSToCoordRound(textRunMetrics.mAdvanceWidth); + } + + virtual void DrawText(nscoord xOffset, nscoord width) + { + gfxPoint point = mPt; + point.x += xOffset; + + // offset is given in terms of left side of string + if (mTextRun->IsRightToLeft()) { + // Bug 581092 - don't use rounded pixel width to advance to + // right-hand end of run, because this will cause different + // glyph positioning for LTR vs RTL drawing of the same + // glyph string on OS X and DWrite where textrun widths may + // involve fractional pixels. + gfxTextRun::Metrics textRunMetrics = + mTextRun->MeasureText(0, + mTextRun->GetLength(), + mDoMeasureBoundingBox ? + gfxFont::TIGHT_INK_EXTENTS : + gfxFont::LOOSE_INK_EXTENTS, + mThebes, + nullptr); + point.x += textRunMetrics.mAdvanceWidth; + // old code was: + // point.x += width * mAppUnitsPerDevPixel; + // TODO: restore this if/when we move to fractional coords + // throughout the text layout process + } + + uint32_t numRuns; + const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); + const uint32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; + const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); + Point baselineOrigin = + Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); + + float advanceSum = 0; + + mCtx->EnsureTarget(); + for (uint32_t c = 0; c < numRuns; c++) { + gfxFont *font = runs[c].mFont; + uint32_t endRun = 0; + if (c + 1 < numRuns) { + endRun = runs[c + 1].mCharacterOffset; + } else { + endRun = mTextRun->GetLength(); + } + + const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); + + RefPtr<ScaledFont> scaledFont = + gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); + + if (!scaledFont) { + // This can occur when something switched DirectWrite off. + return; + } + + GlyphBuffer buffer; + + std::vector<Glyph> glyphBuf; + + for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { + Glyph newGlyph; + if (glyphs[i].IsSimpleGlyph()) { + newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); + if (mTextRun->IsRightToLeft()) { + newGlyph.mPosition.x = baselineOrigin.x - advanceSum - + glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; + } else { + newGlyph.mPosition.x = baselineOrigin.x + advanceSum; + } + newGlyph.mPosition.y = baselineOrigin.y; + advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; + glyphBuf.push_back(newGlyph); + continue; + } + + if (!glyphs[i].GetGlyphCount()) { + continue; + } + + gfxTextRun::DetailedGlyph *detailedGlyphs = + mTextRun->GetDetailedGlyphs(i); + + if (glyphs[i].IsMissing()) { + float xpos; + float advance = detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; + if (mTextRun->IsRightToLeft()) { + xpos = baselineOrigin.x - advanceSum - advance; + } else { + xpos = baselineOrigin.x + advanceSum; + } + advanceSum += advance; + + // default-ignorable characters will have zero advance width. + // we don't draw a hexbox for them, just leave them invisible + if (advance > 0) { + // for now, we use gfxFontMissingGlyphs to draw the hexbox; + // some day we should replace this with a direct Azure version + + // get the DrawTarget's transform, so we can apply it to the + // thebes context for gfxFontMissingGlyphs + Matrix matrix = mCtx->mTarget->GetTransform(); + nsRefPtr<gfxContext> thebes; + if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { + // XXX See bug 808288 comment 5 - Bas says: + // This is a little tricky, potentially this could go wrong if + // we fell back to a Cairo context because of for example + // extremely large Canvas size. Cairo content is technically + // -not- supported, but SupportsAzureContent would return true + // as the browser uses D2D content. + // I'm thinking Cairo content will be good enough to do + // DrawMissingGlyph though. + thebes = new gfxContext(mCtx->mTarget); + } else { + nsRefPtr<gfxASurface> drawSurf; + mCtx->GetThebesSurface(getter_AddRefs(drawSurf)); + thebes = new gfxContext(drawSurf); + } + thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, + matrix._22, matrix._31, matrix._32)); + + gfxFloat height = font->GetMetrics().maxAscent; + gfxRect glyphRect(xpos, baselineOrigin.y - height, + advance, height); + gfxFontMissingGlyphs::DrawMissingGlyph(thebes, glyphRect, + detailedGlyphs[0].mGlyphID); + + mCtx->mTarget->SetTransform(matrix); + } + continue; + } + + for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { + newGlyph.mIndex = detailedGlyphs[c].mGlyphID; + if (mTextRun->IsRightToLeft()) { + newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - + advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + } else { + newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; + } + newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; + glyphBuf.push_back(newGlyph); + advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + } + } + + if (!glyphBuf.size()) { + // This may happen for glyph runs for a 0 size font. + continue; + } + + buffer.mGlyphs = &glyphBuf.front(); + buffer.mNumGlyphs = glyphBuf.size(); + + Rect bounds = mCtx->mTarget->GetTransform(). + TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, + mBoundingBox.width, mBoundingBox.height)); + if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL) { + AdjustedTarget(mCtx, &bounds)-> + FillGlyphs(scaledFont, buffer, + CanvasGeneralPattern(). + ForStyle(mCtx, CanvasRenderingContext2D::STYLE_FILL, mCtx->mTarget), + DrawOptions(mState->globalAlpha, mCtx->UsedOperation())); + } else if (mOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) { + RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); + + const ContextState& state = *mState; + AdjustedTarget(mCtx, &bounds)-> + Stroke(path, CanvasGeneralPattern(). + ForStyle(mCtx, CanvasRenderingContext2D::STYLE_STROKE, mCtx->mTarget), + StrokeOptions(state.lineWidth, state.lineJoin, + state.lineCap, state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset), + DrawOptions(state.globalAlpha, mCtx->UsedOperation())); + + } + } + } + + // current text run + nsAutoPtr<gfxTextRun> mTextRun; + + // pointer to a screen reference context used to measure text and such + nsRefPtr<gfxContext> mThebes; + + // Pointer to the draw target we should fill our text to + CanvasRenderingContext2D *mCtx; + + // position of the left side of the string, alphabetic baseline + gfxPoint mPt; + + // current font + gfxFontGroup* mFontgrp; + + // dev pixel conversion factor + uint32_t mAppUnitsPerDevPixel; + + // operation (fill or stroke) + CanvasRenderingContext2D::TextDrawOperation mOp; + + // context state + ContextState *mState; + + // union of bounding boxes of all runs, needed for shadows + gfxRect mBoundingBox; + + // true iff the bounding box should be measured + bool mDoMeasureBoundingBox; +}; + +nsresult +CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, + float aX, + float aY, + const Optional<double>& aMaxWidth, + TextDrawOperation aOp, + float* aWidth) +{ + nsresult rv; + + if (!FloatValidate(aX, aY) || + (aMaxWidth.WasPassed() && !FloatValidate(aMaxWidth.Value()))) + return NS_ERROR_DOM_SYNTAX_ERR; + + // spec isn't clear on what should happen if aMaxWidth <= 0, so + // treat it as an invalid argument + // technically, 0 should be an invalid value as well, but 0 is the default + // arg, and there is no way to tell if the default was used + if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) + return NS_ERROR_INVALID_ARG; + + if (!mCanvasElement && !mDocShell) { + NS_WARNING("Canvas element must be non-null or a docshell must be provided"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPresShell> presShell = GetPresShell(); + if (!presShell) + return NS_ERROR_FAILURE; + + nsIDocument* document = presShell->GetDocument(); + + // replace all the whitespace characters with U+0020 SPACE + nsAutoString textToDraw(aRawText); + TextReplaceWhitespaceCharacters(textToDraw); + + // for now, default to ltr if not in doc + bool isRTL = false; + + if (mCanvasElement && mCanvasElement->IsInDoc()) { + // try to find the closest context + nsRefPtr<nsStyleContext> canvasStyle = + nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, + nullptr, + presShell); + if (!canvasStyle) { + return NS_ERROR_FAILURE; + } + + isRTL = canvasStyle->GetStyleVisibility()->mDirection == + NS_STYLE_DIRECTION_RTL; + } else { + isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; + } + + gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); + NS_ASSERTION(currentFontStyle, "font group is null"); + + if (currentFontStyle->GetStyle()->size == 0.0F) { + if (aWidth) { + *aWidth = 0; + } + return NS_OK; + } + + const ContextState &state = CurrentState(); + + // This is only needed to know if we can know the drawing bounding box easily. + bool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow(); + + CanvasBidiProcessor processor; + + GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); + processor.mPt = gfxPoint(aX, aY); + processor.mThebes = + new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); + + // If we don't have a target then we don't have a transform. A target won't + // be needed in the case where we're measuring the text size. This allows + // to avoid creating a target if it's only being used to measure text sizes. + if (mTarget) { + Matrix matrix = mTarget->GetTransform(); + processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); + } + processor.mCtx = this; + processor.mOp = aOp; + processor.mBoundingBox = gfxRect(0, 0, 0, 0); + processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; + processor.mState = &CurrentState(); + processor.mFontgrp = currentFontStyle; + + nscoord totalWidthCoord; + + // calls bidi algo twice since it needs the full text width and the + // bounding boxes before rendering anything + nsBidi bidiEngine; + rv = nsBidiPresUtils::ProcessText(textToDraw.get(), + textToDraw.Length(), + isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), + processor, + nsBidiPresUtils::MODE_MEASURE, + nullptr, + 0, + &totalWidthCoord, + &bidiEngine); + if (NS_FAILED(rv)) { + return rv; + } + + float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; + if (aWidth) { + *aWidth = totalWidth; + } + + // if only measuring, don't need to do any more work + if (aOp==TEXT_DRAW_OPERATION_MEASURE) { + return NS_OK; + } + + // offset pt.x based on text align + gfxFloat anchorX; + + if (state.textAlign == TEXT_ALIGN_CENTER) { + anchorX = .5; + } else if (state.textAlign == TEXT_ALIGN_LEFT || + (!isRTL && state.textAlign == TEXT_ALIGN_START) || + (isRTL && state.textAlign == TEXT_ALIGN_END)) { + anchorX = 0; + } else { + anchorX = 1; + } + + processor.mPt.x -= anchorX * totalWidth; + + // offset pt.y based on text baseline + processor.mFontgrp->UpdateFontList(); // ensure user font generation is current + NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); + const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); + + gfxFloat anchorY; + + switch (state.textBaseline) + { + case TEXT_BASELINE_HANGING: + // fall through; best we can do with the information available + case TEXT_BASELINE_TOP: + anchorY = fontMetrics.emAscent; + break; + case TEXT_BASELINE_MIDDLE: + anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; + break; + case TEXT_BASELINE_IDEOGRAPHIC: + // fall through; best we can do with the information available + case TEXT_BASELINE_ALPHABETIC: + anchorY = 0; + break; + case TEXT_BASELINE_BOTTOM: + anchorY = -fontMetrics.emDescent; + break; + default: + MOZ_NOT_REACHED("unexpected TextBaseline"); + } + + processor.mPt.y += anchorY; + + // correct bounding box to get it to be the correct size/position + processor.mBoundingBox.width = totalWidth; + processor.mBoundingBox.MoveBy(processor.mPt); + + processor.mPt.x *= processor.mAppUnitsPerDevPixel; + processor.mPt.y *= processor.mAppUnitsPerDevPixel; + + EnsureTarget(); + Matrix oldTransform = mTarget->GetTransform(); + // if text is over aMaxWidth, then scale the text horizontally such that its + // width is precisely aMaxWidth + if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && + totalWidth > aMaxWidth.Value()) { + Matrix newTransform = oldTransform; + + // Translate so that the anchor point is at 0,0, then scale and then + // translate back. + newTransform.Translate(aX, 0); + newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); + newTransform.Translate(-aX, 0); + /* we do this to avoid an ICE in the android compiler */ + Matrix androidCompilerBug = newTransform; + mTarget->SetTransform(androidCompilerBug); + } + + // save the previous bounding box + gfxRect boundingBox = processor.mBoundingBox; + + // don't ever need to measure the bounding box twice + processor.mDoMeasureBoundingBox = false; + + rv = nsBidiPresUtils::ProcessText(textToDraw.get(), + textToDraw.Length(), + isRTL ? NSBIDI_RTL : NSBIDI_LTR, + presShell->GetPresContext(), + processor, + nsBidiPresUtils::MODE_DRAW, + nullptr, + 0, + nullptr, + &bidiEngine); + + + mTarget->SetTransform(oldTransform); + + if (aOp == CanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL && + !doDrawShadow) { + RedrawUser(boundingBox); + return NS_OK; + } + + Redraw(); + return NS_OK; +} + +gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() +{ + // use lazy initilization for the font group since it's rather expensive + if (!CurrentState().fontGroup) { + ErrorResult err; + SetFont(kDefaultFontStyle, err); + if (err.Failed()) { + gfxFontStyle style; + style.size = kDefaultFontSize; + CurrentState().fontGroup = + gfxPlatform::GetPlatform()->CreateFontGroup(kDefaultFontName, + &style, + nullptr); + if (CurrentState().fontGroup) { + CurrentState().font = kDefaultFontStyle; + } else { + NS_ERROR("Default canvas font is invalid"); + } + } + + } + + return CurrentState().fontGroup; +} + +// +// line caps/joins +// + +void +CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) +{ + CapStyle cap; + + if (capstyle.EqualsLiteral("butt")) { + cap = CAP_BUTT; + } else if (capstyle.EqualsLiteral("round")) { + cap = CAP_ROUND; + } else if (capstyle.EqualsLiteral("square")) { + cap = CAP_SQUARE; + } else { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + return; + } + + CurrentState().lineCap = cap; +} + +void +CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) +{ + switch (CurrentState().lineCap) { + case CAP_BUTT: + capstyle.AssignLiteral("butt"); + break; + case CAP_ROUND: + capstyle.AssignLiteral("round"); + break; + case CAP_SQUARE: + capstyle.AssignLiteral("square"); + break; + } +} + +void +CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) +{ + JoinStyle j; + + if (joinstyle.EqualsLiteral("round")) { + j = JOIN_ROUND; + } else if (joinstyle.EqualsLiteral("bevel")) { + j = JOIN_BEVEL; + } else if (joinstyle.EqualsLiteral("miter")) { + j = JOIN_MITER_OR_BEVEL; + } else { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + return; + } + + CurrentState().lineJoin = j; +} + +void +CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) +{ + switch (CurrentState().lineJoin) { + case JOIN_ROUND: + joinstyle.AssignLiteral("round"); + break; + case JOIN_BEVEL: + joinstyle.AssignLiteral("bevel"); + break; + case JOIN_MITER_OR_BEVEL: + joinstyle.AssignLiteral("miter"); + break; + default: + error.Throw(NS_ERROR_FAILURE); + } +} + +void +CanvasRenderingContext2D::SetMozDash(JSContext* cx, + const JS::Value& mozDash, + ErrorResult& error) +{ + FallibleTArray<Float> dash; + error = JSValToDashArray(cx, mozDash, dash); + if (!error.Failed()) { + ContextState& state = CurrentState(); + state.dash = dash; + if (state.dash.IsEmpty()) { + state.dashOffset = 0; + } + } +} + +JS::Value +CanvasRenderingContext2D::GetMozDash(JSContext* cx, ErrorResult& error) +{ + JS::Value mozDash; + error = DashArrayToJSVal(CurrentState().dash, cx, &mozDash); + return mozDash; +} + +void +CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) +{ + if (!FloatValidate(mozDashOffset)) { + return; + } + + ContextState& state = CurrentState(); if (!state.dash.IsEmpty()) { state.dashOffset = mozDashOffset; - } -} - -bool -CanvasRenderingContext2D::IsPointInPath(double x, double y) -{ - if (!FloatValidate(x,y)) { - return false; - } - - EnsureUserSpacePath(false); - if (!mPath) { - return false; - } - if (mPathTransformWillUpdate) { - return mPath->ContainsPoint(Point(x, y), mPathToDS); - } - return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); -} - -bool -CanvasRenderingContext2D::MozIsPointInStroke(double x, double y) -{ - if (!FloatValidate(x,y)) { - return false; - } - - EnsureUserSpacePath(false); - if (!mPath) { - return false; - } - - const ContextState &state = CurrentState(); - - StrokeOptions strokeOptions(state.lineWidth, - state.lineJoin, - state.lineCap, - state.miterLimit, - state.dash.Length(), - state.dash.Elements(), - state.dashOffset); - - if (mPathTransformWillUpdate) { - return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); - } - return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); -} - -// -// image -// - -// drawImage(in HTMLImageElement image, in float dx, in float dy); -// -- render image from 0,0 at dx,dy top-left coords -// drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); -// -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh -// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); -// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas - -// If only dx and dy are passed in then optional_argc should be 0. If only -// dx, dy, dw and dh are passed in then optional_argc should be 2. The only -// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh -// are all passed in. - -void -CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, - double sx, double sy, double sw, - double sh, double dx, double dy, - double dw, double dh, - uint8_t optional_argc, - ErrorResult& error) -{ - MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); - - RefPtr<SourceSurface> srcSurf; - gfxIntSize imgSize; - - Element* element; - - EnsureTarget(); - if (image.IsHTMLCanvasElement()) { - nsHTMLCanvasElement* canvas = image.GetAsHTMLCanvasElement(); - element = canvas; - nsIntSize size = canvas->GetSize(); - if (size.width == 0 || size.height == 0) { - error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return; - } - - // Special case for Canvas, which could be an Azure canvas! - nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); - if (srcCanvas == this) { - // Self-copy. - srcSurf = mTarget->Snapshot(); - imgSize = gfxIntSize(mWidth, mHeight); - } else if (srcCanvas) { - // This might not be an Azure canvas! - srcSurf = srcCanvas->GetSurfaceSnapshot(); - - if (srcSurf) { - if (mCanvasElement) { - // Do security check here. - CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, - element->NodePrincipal(), - canvas->IsWriteOnly(), - false); - } - imgSize = gfxIntSize(srcSurf->GetSize().width, srcSurf->GetSize().height); - } - } - } else { - if (image.IsHTMLImageElement()) { - nsHTMLImageElement* img = image.GetAsHTMLImageElement(); - element = img; - } else { - nsHTMLVideoElement* video = image.GetAsHTMLVideoElement(); - element = video; - } - - gfxASurface* imgsurf = - CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); - if (imgsurf) { - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf); - } - } - - if (!srcSurf) { - // The canvas spec says that drawImage should draw the first frame - // of animated images - uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME; - nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(element, sfeFlags); - - if (!res.mSurface) { - // Spec says to silently do nothing if the element is still loading. - if (!res.mIsStillLoading) { - error.Throw(NS_ERROR_NOT_AVAILABLE); - } - return; - } - - // Ignore cairo surfaces that are bad! See bug 666312. - if (res.mSurface->CairoStatus()) { - return; - } - - imgSize = res.mSize; - - if (mCanvasElement) { - CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, - res.mPrincipal, res.mIsWriteOnly, - res.mCORSUsed); - } - - if (res.mImageRequest) { - CanvasImageCache::NotifyDrawImage(element, mCanvasElement, - res.mImageRequest, res.mSurface, imgSize); - } - - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); - } - - if (optional_argc == 0) { - sx = sy = 0.0; - dw = sw = (double) imgSize.width; - dh = sh = (double) imgSize.height; - } else if (optional_argc == 2) { - sx = sy = 0.0; - sw = (double) imgSize.width; - sh = (double) imgSize.height; - } - - if (sw == 0.0 || sh == 0.0) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - if (dw == 0.0 || dh == 0.0) { - // not really failure, but nothing to do -- - // and noone likes a divide-by-zero - return; - } - - if (sx < 0.0 || sy < 0.0 || - sw < 0.0 || sw > (double) imgSize.width || - sh < 0.0 || sh > (double) imgSize.height || - dw < 0.0 || dh < 0.0) { - // XXX - Unresolved spec issues here, for now return error. - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return; - } - - Filter filter; - - if (CurrentState().imageSmoothingEnabled) - filter = mgfx::FILTER_LINEAR; - else - filter = mgfx::FILTER_POINT; - - mgfx::Rect bounds; - - if (NeedToDrawShadow()) { - bounds = mgfx::Rect(dx, dy, dw, dh); - bounds = mTarget->GetTransform().TransformBounds(bounds); - } - - AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> - DrawSurface(srcSurf, - mgfx::Rect(dx, dy, dw, dh), - mgfx::Rect(sx, sy, sw, sh), - DrawSurfaceOptions(filter), - DrawOptions(CurrentState().globalAlpha, UsedOperation())); - - RedrawUser(gfxRect(dx, dy, dw, dh)); -} - -void -CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, - ErrorResult& error) -{ - CompositionOp comp_op; - -#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ - if (op.EqualsLiteral(cvsop)) \ - comp_op = OP_##op2d; - - CANVAS_OP_TO_GFX_OP("copy", SOURCE) - else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) - else CANVAS_OP_TO_GFX_OP("source-in", IN) - else CANVAS_OP_TO_GFX_OP("source-out", OUT) - else CANVAS_OP_TO_GFX_OP("source-over", OVER) - else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) - else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) - else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) - else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) - else CANVAS_OP_TO_GFX_OP("lighter", ADD) - else CANVAS_OP_TO_GFX_OP("xor", XOR) - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - else return; - -#undef CANVAS_OP_TO_GFX_OP - CurrentState().op = comp_op; -} - -void -CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, - ErrorResult& error) -{ - CompositionOp comp_op = CurrentState().op; - -#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ - if (comp_op == OP_##op2d) \ - op.AssignLiteral(cvsop); - - CANVAS_OP_TO_GFX_OP("copy", SOURCE) - else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) - else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) - else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) - else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) - else CANVAS_OP_TO_GFX_OP("lighter", ADD) - else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) - else CANVAS_OP_TO_GFX_OP("source-in", IN) - else CANVAS_OP_TO_GFX_OP("source-out", OUT) - else CANVAS_OP_TO_GFX_OP("source-over", OVER) - else CANVAS_OP_TO_GFX_OP("xor", XOR) - else { - error.Throw(NS_ERROR_FAILURE); - } - -#undef CANVAS_OP_TO_GFX_OP -} - -void -CanvasRenderingContext2D::DrawWindow(nsIDOMWindow* window, double x, - double y, double w, double h, - const nsAString& bgColor, - uint32_t flags, ErrorResult& error) -{ - // protect against too-large surfaces that will cause allocation - // or overflow issues - if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), - 0xffff)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - EnsureTarget(); - // We can't allow web apps to call this until we fix at least the - // following potential security issues: - // -- rendering cross-domain IFRAMEs and then extracting the results - // -- rendering the user's theme and then extracting the results - // -- rendering native anonymous content (e.g., file input paths; - // scrollbars should be allowed) - if (!nsContentUtils::IsCallerChrome()) { - // not permitted to use DrawWindow - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return; - } - - // Flush layout updates - if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { - nsContentUtils::FlushLayoutForTree(window); - } - - nsRefPtr<nsPresContext> presContext; - nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(window); - if (win) { - nsIDocShell* docshell = win->GetDocShell(); - if (docshell) { - docshell->GetPresContext(getter_AddRefs(presContext)); - } - } - if (!presContext) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nscolor backgroundColor; - if (!ParseColor(bgColor, &backgroundColor)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), - nsPresContext::CSSPixelsToAppUnits((float)y), - nsPresContext::CSSPixelsToAppUnits((float)w), - nsPresContext::CSSPixelsToAppUnits((float)h)); - uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | - nsIPresShell::RENDER_DOCUMENT_RELATIVE); - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { - renderDocFlags |= nsIPresShell::RENDER_CARET; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { - renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | - nsIPresShell::RENDER_DOCUMENT_RELATIVE); - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { - renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { - renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; - } - - // gfxContext-over-Azure may modify the DrawTarget's transform, so - // save and restore it - Matrix matrix = mTarget->GetTransform(); - nsRefPtr<gfxContext> thebes; - if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { - thebes = new gfxContext(mTarget); - } else { - nsRefPtr<gfxASurface> drawSurf; - GetThebesSurface(getter_AddRefs(drawSurf)); - thebes = new gfxContext(drawSurf); - } - thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, - matrix._22, matrix._31, matrix._32)); - nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); - unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); - mTarget->SetTransform(matrix); - - // note that x and y are coordinates in the document that - // we're drawing; x and y are drawn to 0,0 in current user - // space. - RedrawUser(gfxRect(0, 0, w, h)); -} - -void -CanvasRenderingContext2D::AsyncDrawXULElement(nsIDOMXULElement* elem, - double x, double y, - double w, double h, - const nsAString& bgColor, - uint32_t flags, - ErrorResult& error) -{ - // We can't allow web apps to call this until we fix at least the - // following potential security issues: - // -- rendering cross-domain IFRAMEs and then extracting the results - // -- rendering the user's theme and then extracting the results - // -- rendering native anonymous content (e.g., file input paths; - // scrollbars should be allowed) - if (!nsContentUtils::IsCallerChrome()) { - // not permitted to use DrawWindow - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return; - } - -#if 0 - nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(elem); - if (!loaderOwner) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - nsRefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader(); - if (!frameloader) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - PBrowserParent *child = frameloader->GetRemoteBrowser(); - if (!child) { - nsCOMPtr<nsIDOMWindow> window = - do_GetInterface(frameloader->GetExistingDocShell()); - if (!window) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - return DrawWindow(window, x, y, w, h, bgColor, flags); - } - - // protect against too-large surfaces that will cause allocation - // or overflow issues - if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { - error.Throw(NS_ERROR_FAILURE); - return; - } - - bool flush = - (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; - - uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { - renderDocFlags |= nsIPresShell::RENDER_CARET; - } - if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { - renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; - } - - nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), - nsPresContext::CSSPixelsToAppUnits(y), - nsPresContext::CSSPixelsToAppUnits(w), - nsPresContext::CSSPixelsToAppUnits(h)); - if (mIPC) { - PDocumentRendererParent *pdocrender = - child->SendPDocumentRendererConstructor(rect, - mThebes->CurrentMatrix(), - nsString(aBGColor), - renderDocFlags, flush, - nsIntSize(mWidth, mHeight)); - if (!pdocrender) - return NS_ERROR_FAILURE; - - DocumentRendererParent *docrender = - static_cast<DocumentRendererParent *>(pdocrender); - - docrender->SetCanvasContext(this, mThebes); - } -#endif -} - -// -// device pixel getting/setting -// - -void -CanvasRenderingContext2D::EnsureUnpremultiplyTable() { - if (sUnpremultiplyTable) - return; - - // Infallably alloc the unpremultiply table. - sUnpremultiplyTable = new uint8_t[256][256]; - - // It's important that the array be indexed first by alpha and then by rgb - // value. When we unpremultiply a pixel, we're guaranteed to do three - // lookups with the same alpha; indexing by alpha first makes it likely that - // those three lookups will be close to one another in memory, thus - // increasing the chance of a cache hit. - - // a == 0 case - for (uint32_t c = 0; c <= 255; c++) { - sUnpremultiplyTable[0][c] = c; - } - - for (int a = 1; a <= 255; a++) { - for (int c = 0; c <= 255; c++) { - sUnpremultiplyTable[a][c] = (uint8_t)((c * 255) / a); - } - } -} - - -already_AddRefed<ImageData> -CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, - double aSy, double aSw, - double aSh, ErrorResult& error) -{ - EnsureTarget(); - if (!IsTargetValid()) { - error.Throw(NS_ERROR_FAILURE); - return NULL; - } - - if (!mCanvasElement && !mDocShell) { - NS_ERROR("No canvas element and no docshell in GetImageData!!!"); - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return NULL; - } - - // Check only if we have a canvas element; if we were created with a docshell, - // then it's special internal use. - if (mCanvasElement && mCanvasElement->IsWriteOnly() && - !nsContentUtils::IsCallerChrome()) - { - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - error.Throw(NS_ERROR_DOM_SECURITY_ERR); - return NULL; - } - - if (!NS_finite(aSx) || !NS_finite(aSy) || - !NS_finite(aSw) || !NS_finite(aSh)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return NULL; - } - - if (!aSw || !aSh) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - int32_t x = JS_DoubleToInt32(aSx); - int32_t y = JS_DoubleToInt32(aSy); - int32_t wi = JS_DoubleToInt32(aSw); - int32_t hi = JS_DoubleToInt32(aSh); - - // Handle negative width and height by flipping the rectangle over in the - // relevant direction. - uint32_t w, h; - if (aSw < 0) { - w = -wi; - x -= w; - } else { - w = wi; - } - if (aSh < 0) { - h = -hi; - y -= h; - } else { - h = hi; - } - - if (w == 0) { - w = 1; - } - if (h == 0) { - h = 1; - } - - JSObject* array; - error = GetImageDataArray(aCx, x, y, w, h, &array); - if (error.Failed()) { - return NULL; - } - MOZ_ASSERT(array); - - nsRefPtr<ImageData> imageData = new ImageData(w, h, *array); - return imageData.forget(); -} - -nsresult -CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, - int32_t aX, - int32_t aY, - uint32_t aWidth, - uint32_t aHeight, - JSObject** aRetval) -{ - MOZ_ASSERT(aWidth && aHeight); - - CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; - if (!len.isValid()) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth; - CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight; - - if (!rightMost.isValid() || !bottomMost.isValid()) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - JSObject* darray = JS_NewUint8ClampedArray(aCx, len.value()); - if (!darray) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (mZero) { - *aRetval = darray; - return NS_OK; - } - - uint8_t* data = JS_GetUint8ClampedArrayData(darray); - - IntRect srcRect(0, 0, mWidth, mHeight); - IntRect destRect(aX, aY, aWidth, aHeight); - - IntRect srcReadRect = srcRect.Intersect(destRect); - IntRect dstWriteRect = srcReadRect; - dstWriteRect.MoveBy(-aX, -aY); - - uint8_t* src = data; - uint32_t srcStride = aWidth * 4; - - RefPtr<DataSourceSurface> readback; - if (!srcReadRect.IsEmpty()) { - RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); - if (snapshot) { - readback = snapshot->GetDataSurface(); - - srcStride = readback->Stride(); - src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; - } - } - - // make sure sUnpremultiplyTable has been created - EnsureUnpremultiplyTable(); - - // NOTE! dst is the same as src, and this relies on reading - // from src and advancing that ptr before writing to dst. - // NOTE! I'm not sure that it is, I think this comment might have been - // inherited from Thebes canvas and is no longer true - uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; - - for (int32_t j = 0; j < dstWriteRect.height; ++j) { - for (int32_t i = 0; i < dstWriteRect.width; ++i) { - // XXX Is there some useful swizzle MMX we can use here? -#ifdef IS_LITTLE_ENDIAN - uint8_t b = *src++; - uint8_t g = *src++; - uint8_t r = *src++; - uint8_t a = *src++; -#else - uint8_t a = *src++; - uint8_t r = *src++; - uint8_t g = *src++; - uint8_t b = *src++; -#endif - // Convert to non-premultiplied color - *dst++ = sUnpremultiplyTable[a][r]; - *dst++ = sUnpremultiplyTable[a][g]; - *dst++ = sUnpremultiplyTable[a][b]; - *dst++ = a; - } - src += srcStride - (dstWriteRect.width * 4); - dst += (aWidth * 4) - (dstWriteRect.width * 4); - } - - *aRetval = darray; - return NS_OK; -} - -void -CanvasRenderingContext2D::EnsurePremultiplyTable() { - if (sPremultiplyTable) - return; - - // Infallably alloc the premultiply table. - sPremultiplyTable = new uint8_t[256][256]; - - // Like the unpremultiply table, it's important that we index the premultiply - // table with the alpha value as the first index to ensure good cache - // performance. - - for (int a = 0; a <= 255; a++) { - for (int c = 0; c <= 255; c++) { - sPremultiplyTable[a][c] = (a * c + 254) / 255; - } - } -} - -void -CanvasRenderingContext2D::EnsureErrorTarget() -{ - if (sErrorTarget) { - return; - } - - RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8); - NS_ABORT_IF_FALSE(errorTarget, "Failed to allocate the error target!"); - - sErrorTarget = errorTarget; - NS_ADDREF(sErrorTarget); -} - -void -CanvasRenderingContext2D::FillRuleChanged() -{ - if (mPath) { - mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); - mPath = nullptr; - } -} - -void -CanvasRenderingContext2D::PutImageData(JSContext* cx, - ImageData& imageData, double dx, - double dy, ErrorResult& error) -{ - if (!FloatValidate(dx, dy)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return; - } - - dom::Uint8ClampedArray arr(imageData.GetDataObject()); - - error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), - imageData.Width(), imageData.Height(), - arr.Data(), arr.Length(), false, 0, 0, 0, 0); -} - -void -CanvasRenderingContext2D::PutImageData(JSContext* cx, - ImageData& imageData, double dx, - double dy, double dirtyX, - double dirtyY, double dirtyWidth, - double dirtyHeight, - ErrorResult& error) -{ - if (!FloatValidate(dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return; - } - - dom::Uint8ClampedArray arr(imageData.GetDataObject()); - - error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), - imageData.Width(), imageData.Height(), - arr.Data(), arr.Length(), true, - JS_DoubleToInt32(dirtyX), - JS_DoubleToInt32(dirtyY), - JS_DoubleToInt32(dirtyWidth), - JS_DoubleToInt32(dirtyHeight)); -} - -// void putImageData (in ImageData d, in float x, in float y); -// void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight); - -nsresult -CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, - unsigned char *aData, uint32_t aDataLen, - bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, - int32_t dirtyWidth, int32_t dirtyHeight) -{ - if (w == 0 || h == 0) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - IntRect dirtyRect; - IntRect imageDataRect(0, 0, w, h); - - if (hasDirtyRect) { - // fix up negative dimensions - if (dirtyWidth < 0) { - NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); - - CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; - - if (!checkedDirtyX.isValid()) - return NS_ERROR_DOM_INDEX_SIZE_ERR; - - dirtyX = checkedDirtyX.value(); - dirtyWidth = -dirtyWidth; - } - - if (dirtyHeight < 0) { - NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); - - CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; - - if (!checkedDirtyY.isValid()) - return NS_ERROR_DOM_INDEX_SIZE_ERR; - - dirtyY = checkedDirtyY.value(); - dirtyHeight = -dirtyHeight; - } - - // bound the dirty rect within the imageData rectangle - dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); - - if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) - return NS_OK; - } else { - dirtyRect = imageDataRect; - } - - dirtyRect.MoveBy(IntPoint(x, y)); - dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); - - if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { - return NS_OK; - } - - uint32_t len = w * h * 4; - if (aDataLen != len) { - return NS_ERROR_DOM_SYNTAX_ERR; - } - - nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(gfxIntSize(w, h), - gfxASurface::ImageFormatARGB32, - false); - if (!imgsurf || imgsurf->CairoStatus()) { - return NS_ERROR_FAILURE; - } - - // ensure premultiply table has been created - EnsurePremultiplyTable(); - - uint8_t *src = aData; - uint8_t *dst = imgsurf->Data(); - - for (uint32_t j = 0; j < h; j++) { - for (uint32_t i = 0; i < w; i++) { - uint8_t r = *src++; - uint8_t g = *src++; - uint8_t b = *src++; - uint8_t a = *src++; - - // Convert to premultiplied color (losslessly if the input came from getImageData) -#ifdef IS_LITTLE_ENDIAN - *dst++ = sPremultiplyTable[a][b]; - *dst++ = sPremultiplyTable[a][g]; - *dst++ = sPremultiplyTable[a][r]; - *dst++ = a; -#else - *dst++ = a; - *dst++ = sPremultiplyTable[a][r]; - *dst++ = sPremultiplyTable[a][g]; - *dst++ = sPremultiplyTable[a][b]; -#endif - } - } - - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } - - RefPtr<SourceSurface> sourceSurface = - mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), FORMAT_B8G8R8A8); - - - mTarget->CopySurface(sourceSurface, - IntRect(dirtyRect.x - x, dirtyRect.y - y, - dirtyRect.width, dirtyRect.height), - IntPoint(dirtyRect.x, dirtyRect.y)); - - Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); - - return NS_OK; -} - -NS_IMETHODIMP -CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface) -{ - EnsureTarget(); - if (!mThebesSurface) { - mThebesSurface = - gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); - - if (!mThebesSurface) { - return NS_ERROR_FAILURE; - } - } else { - // Normally GetThebesSurfaceForDrawTarget will handle the flush, when - // we're returning a cached ThebesSurface we need to flush here. - mTarget->Flush(); - } - - *surface = mThebesSurface; - NS_ADDREF(*surface); - - return NS_OK; -} - -static already_AddRefed<ImageData> -CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, - uint32_t w, uint32_t h, ErrorResult& error) -{ - if (w == 0) - w = 1; - if (h == 0) - h = 1; - - CheckedInt<uint32_t> len = CheckedInt<uint32_t>(w) * h * 4; - if (!len.isValid()) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - // Create the fast typed array; it's initialized to 0 by default. - JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); - if (!darray) { - error.Throw(NS_ERROR_OUT_OF_MEMORY); - return NULL; - } - - nsRefPtr<mozilla::dom::ImageData> imageData = - new mozilla::dom::ImageData(w, h, *darray); - return imageData.forget(); -} - -already_AddRefed<ImageData> -CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, - double sh, ErrorResult& error) -{ - if (!FloatValidate(sw, sh)) { - error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); - return NULL; - } - - if (!sw || !sh) { - error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); - return NULL; - } - - int32_t wi = JS_DoubleToInt32(sw); - int32_t hi = JS_DoubleToInt32(sh); - - uint32_t w = NS_ABS(wi); - uint32_t h = NS_ABS(hi); - return mozilla::dom::CreateImageData(cx, this, w, h, error); -} - -already_AddRefed<ImageData> -CanvasRenderingContext2D::CreateImageData(JSContext* cx, - ImageData& imagedata, - ErrorResult& error) -{ - return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), - imagedata.Height(), error); -} - -static uint8_t g2DContextLayerUserData; - -already_AddRefed<CanvasLayer> -CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, - CanvasLayer *aOldLayer, - LayerManager *aManager) -{ - // Don't call EnsureTarget() ... if there isn't already a surface, then - // we have nothing to paint and there is no need to create a surface just - // to paint nothing. Also, EnsureTarget() can cause creation of a persistent - // layer manager which must NOT happen during a paint. - if (!mTarget || !IsTargetValid()) { - // No DidTransactionCallback will be received, so mark the context clean - // now so future invalidations will be dispatched. - MarkContextClean(); - return nullptr; - } - - mTarget->Flush(); - - if (!mResetLayer && aOldLayer) { - CanvasRenderingContext2DUserData* userData = - static_cast<CanvasRenderingContext2DUserData*>( - aOldLayer->GetUserData(&g2DContextLayerUserData)); - if (userData && userData->IsForContext(this)) { - NS_ADDREF(aOldLayer); - return aOldLayer; - } - } - - nsRefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer(); - if (!canvasLayer) { - NS_WARNING("CreateCanvasLayer returned null!"); - // No DidTransactionCallback will be received, so mark the context clean - // now so future invalidations will be dispatched. - MarkContextClean(); - return nullptr; - } - CanvasRenderingContext2DUserData *userData = nullptr; - // Make the layer tell us whenever a transaction finishes (including - // the current transaction), so we can clear our invalidation state and - // start invalidating again. We need to do this for all layers since - // callers of DrawWindow may be expecting to receive normal invalidation - // notifications after this paint. - - // The layer will be destroyed when we tear down the presentation - // (at the latest), at which time this userData will be destroyed, - // releasing the reference to the element. - // The userData will receive DidTransactionCallbacks, which flush the - // the invalidation state to indicate that the canvas is up to date. - userData = new CanvasRenderingContext2DUserData(this); - canvasLayer->SetDidTransactionCallback( - CanvasRenderingContext2DUserData::DidTransactionCallback, userData); - canvasLayer->SetUserData(&g2DContextLayerUserData, userData); - - CanvasLayer::Data data; - - data.mDrawTarget = mTarget; - data.mSize = nsIntSize(mWidth, mHeight); - - canvasLayer->Initialize(data); - uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; - canvasLayer->SetContentFlags(flags); - canvasLayer->Updated(); - - mResetLayer = false; - - return canvasLayer.forget(); -} - -void -CanvasRenderingContext2D::MarkContextClean() -{ - if (mInvalidateCount > 0) { - mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; - } - mIsEntireFrameInvalid = false; - mInvalidateCount = 0; -} - - -bool -CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) -{ - return !aManager->CanUseCanvasLayerForSize(gfxIntSize(mWidth, mHeight)); -} - -} -} - -DOMCI_DATA(TextMetrics, mozilla::dom::TextMetrics) -DOMCI_DATA(CanvasGradient, mozilla::dom::CanvasGradient) -DOMCI_DATA(CanvasPattern, mozilla::dom::CanvasPattern) -DOMCI_DATA(CanvasRenderingContext2D, mozilla::dom::CanvasRenderingContext2D) - + } +} + +bool +CanvasRenderingContext2D::IsPointInPath(double x, double y) +{ + if (!FloatValidate(x,y)) { + return false; + } + + EnsureUserSpacePath(false); + if (!mPath) { + return false; + } + if (mPathTransformWillUpdate) { + return mPath->ContainsPoint(Point(x, y), mPathToDS); + } + return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); +} + +bool +CanvasRenderingContext2D::MozIsPointInStroke(double x, double y) +{ + if (!FloatValidate(x,y)) { + return false; + } + + EnsureUserSpacePath(false); + if (!mPath) { + return false; + } + + const ContextState &state = CurrentState(); + + StrokeOptions strokeOptions(state.lineWidth, + state.lineJoin, + state.lineCap, + state.miterLimit, + state.dash.Length(), + state.dash.Elements(), + state.dashOffset); + + if (mPathTransformWillUpdate) { + return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); + } + return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); +} + +// +// image +// + +// drawImage(in HTMLImageElement image, in float dx, in float dy); +// -- render image from 0,0 at dx,dy top-left coords +// drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); +// -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh +// drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); +// -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas + +// If only dx and dy are passed in then optional_argc should be 0. If only +// dx, dy, dw and dh are passed in then optional_argc should be 2. The only +// other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh +// are all passed in. + +void +CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, + double sx, double sy, double sw, + double sh, double dx, double dy, + double dw, double dh, + uint8_t optional_argc, + ErrorResult& error) +{ + MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); + + RefPtr<SourceSurface> srcSurf; + gfxIntSize imgSize; + + Element* element; + + EnsureTarget(); + if (image.IsHTMLCanvasElement()) { + nsHTMLCanvasElement* canvas = image.GetAsHTMLCanvasElement(); + element = canvas; + nsIntSize size = canvas->GetSize(); + if (size.width == 0 || size.height == 0) { + error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + // Special case for Canvas, which could be an Azure canvas! + nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); + if (srcCanvas == this) { + // Self-copy. + srcSurf = mTarget->Snapshot(); + imgSize = gfxIntSize(mWidth, mHeight); + } else if (srcCanvas) { + // This might not be an Azure canvas! + srcSurf = srcCanvas->GetSurfaceSnapshot(); + + if (srcSurf) { + if (mCanvasElement) { + // Do security check here. + CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, + element->NodePrincipal(), + canvas->IsWriteOnly(), + false); + } + imgSize = gfxIntSize(srcSurf->GetSize().width, srcSurf->GetSize().height); + } + } + } else { + if (image.IsHTMLImageElement()) { + nsHTMLImageElement* img = image.GetAsHTMLImageElement(); + element = img; + } else { + nsHTMLVideoElement* video = image.GetAsHTMLVideoElement(); + element = video; + } + + gfxASurface* imgsurf = + CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); + if (imgsurf) { + srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf); + } + } + + if (!srcSurf) { + // The canvas spec says that drawImage should draw the first frame + // of animated images + uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME; + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(element, sfeFlags); + + if (!res.mSurface) { + // Spec says to silently do nothing if the element is still loading. + if (!res.mIsStillLoading) { + error.Throw(NS_ERROR_NOT_AVAILABLE); + } + return; + } + + // Ignore cairo surfaces that are bad! See bug 666312. + if (res.mSurface->CairoStatus()) { + return; + } + + imgSize = res.mSize; + + if (mCanvasElement) { + CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, + res.mPrincipal, res.mIsWriteOnly, + res.mCORSUsed); + } + + if (res.mImageRequest) { + CanvasImageCache::NotifyDrawImage(element, mCanvasElement, + res.mImageRequest, res.mSurface, imgSize); + } + + srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); + } + + if (optional_argc == 0) { + sx = sy = 0.0; + dw = sw = (double) imgSize.width; + dh = sh = (double) imgSize.height; + } else if (optional_argc == 2) { + sx = sy = 0.0; + sw = (double) imgSize.width; + sh = (double) imgSize.height; + } + + if (sw == 0.0 || sh == 0.0) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + if (dw == 0.0 || dh == 0.0) { + // not really failure, but nothing to do -- + // and noone likes a divide-by-zero + return; + } + + if (sx < 0.0 || sy < 0.0 || + sw < 0.0 || sw > (double) imgSize.width || + sh < 0.0 || sh > (double) imgSize.height || + dw < 0.0 || dh < 0.0) { + // XXX - Unresolved spec issues here, for now return error. + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + Filter filter; + + if (CurrentState().imageSmoothingEnabled) + filter = mgfx::FILTER_LINEAR; + else + filter = mgfx::FILTER_POINT; + + mgfx::Rect bounds; + + if (NeedToDrawShadow()) { + bounds = mgfx::Rect(dx, dy, dw, dh); + bounds = mTarget->GetTransform().TransformBounds(bounds); + } + + AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> + DrawSurface(srcSurf, + mgfx::Rect(dx, dy, dw, dh), + mgfx::Rect(sx, sy, sw, sh), + DrawSurfaceOptions(filter), + DrawOptions(CurrentState().globalAlpha, UsedOperation())); + + RedrawUser(gfxRect(dx, dy, dw, dh)); +} + +void +CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, + ErrorResult& error) +{ + CompositionOp comp_op; + +#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ + if (op.EqualsLiteral(cvsop)) \ + comp_op = OP_##op2d; + + CANVAS_OP_TO_GFX_OP("copy", SOURCE) + else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) + else CANVAS_OP_TO_GFX_OP("source-in", IN) + else CANVAS_OP_TO_GFX_OP("source-out", OUT) + else CANVAS_OP_TO_GFX_OP("source-over", OVER) + else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) + else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) + else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) + else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) + else CANVAS_OP_TO_GFX_OP("lighter", ADD) + else CANVAS_OP_TO_GFX_OP("xor", XOR) + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + else return; + +#undef CANVAS_OP_TO_GFX_OP + CurrentState().op = comp_op; +} + +void +CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, + ErrorResult& error) +{ + CompositionOp comp_op = CurrentState().op; + +#define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ + if (comp_op == OP_##op2d) \ + op.AssignLiteral(cvsop); + + CANVAS_OP_TO_GFX_OP("copy", SOURCE) + else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) + else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) + else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) + else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) + else CANVAS_OP_TO_GFX_OP("lighter", ADD) + else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) + else CANVAS_OP_TO_GFX_OP("source-in", IN) + else CANVAS_OP_TO_GFX_OP("source-out", OUT) + else CANVAS_OP_TO_GFX_OP("source-over", OVER) + else CANVAS_OP_TO_GFX_OP("xor", XOR) + else { + error.Throw(NS_ERROR_FAILURE); + } + +#undef CANVAS_OP_TO_GFX_OP +} + +void +CanvasRenderingContext2D::DrawWindow(nsIDOMWindow* window, double x, + double y, double w, double h, + const nsAString& bgColor, + uint32_t flags, ErrorResult& error) +{ + // protect against too-large surfaces that will cause allocation + // or overflow issues + if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), + 0xffff)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + EnsureTarget(); + // We can't allow web apps to call this until we fix at least the + // following potential security issues: + // -- rendering cross-domain IFRAMEs and then extracting the results + // -- rendering the user's theme and then extracting the results + // -- rendering native anonymous content (e.g., file input paths; + // scrollbars should be allowed) + if (!nsContentUtils::IsCallerChrome()) { + // not permitted to use DrawWindow + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + // Flush layout updates + if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { + nsContentUtils::FlushLayoutForTree(window); + } + + nsRefPtr<nsPresContext> presContext; + nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(window); + if (win) { + nsIDocShell* docshell = win->GetDocShell(); + if (docshell) { + docshell->GetPresContext(getter_AddRefs(presContext)); + } + } + if (!presContext) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nscolor backgroundColor; + if (!ParseColor(bgColor, &backgroundColor)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), + nsPresContext::CSSPixelsToAppUnits((float)y), + nsPresContext::CSSPixelsToAppUnits((float)w), + nsPresContext::CSSPixelsToAppUnits((float)h)); + uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | + nsIPresShell::RENDER_DOCUMENT_RELATIVE); + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { + renderDocFlags |= nsIPresShell::RENDER_CARET; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { + renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | + nsIPresShell::RENDER_DOCUMENT_RELATIVE); + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { + renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { + renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; + } + + // gfxContext-over-Azure may modify the DrawTarget's transform, so + // save and restore it + Matrix matrix = mTarget->GetTransform(); + nsRefPtr<gfxContext> thebes; + if (gfxPlatform::GetPlatform()->SupportsAzureContent()) { + thebes = new gfxContext(mTarget); + } else { + nsRefPtr<gfxASurface> drawSurf; + GetThebesSurface(getter_AddRefs(drawSurf)); + thebes = new gfxContext(drawSurf); + } + thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, + matrix._22, matrix._31, matrix._32)); + nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); + unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); + mTarget->SetTransform(matrix); + + // note that x and y are coordinates in the document that + // we're drawing; x and y are drawn to 0,0 in current user + // space. + RedrawUser(gfxRect(0, 0, w, h)); +} + +void +CanvasRenderingContext2D::AsyncDrawXULElement(nsIDOMXULElement* elem, + double x, double y, + double w, double h, + const nsAString& bgColor, + uint32_t flags, + ErrorResult& error) +{ + // We can't allow web apps to call this until we fix at least the + // following potential security issues: + // -- rendering cross-domain IFRAMEs and then extracting the results + // -- rendering the user's theme and then extracting the results + // -- rendering native anonymous content (e.g., file input paths; + // scrollbars should be allowed) + if (!nsContentUtils::IsCallerChrome()) { + // not permitted to use DrawWindow + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + +#if 0 + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(elem); + if (!loaderOwner) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + nsRefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader(); + if (!frameloader) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + PBrowserParent *child = frameloader->GetRemoteBrowser(); + if (!child) { + nsCOMPtr<nsIDOMWindow> window = + do_GetInterface(frameloader->GetExistingDocShell()); + if (!window) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + return DrawWindow(window, x, y, w, h, bgColor, flags); + } + + // protect against too-large surfaces that will cause allocation + // or overflow issues + if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { + error.Throw(NS_ERROR_FAILURE); + return; + } + + bool flush = + (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; + + uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { + renderDocFlags |= nsIPresShell::RENDER_CARET; + } + if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { + renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; + } + + nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), + nsPresContext::CSSPixelsToAppUnits(y), + nsPresContext::CSSPixelsToAppUnits(w), + nsPresContext::CSSPixelsToAppUnits(h)); + if (mIPC) { + PDocumentRendererParent *pdocrender = + child->SendPDocumentRendererConstructor(rect, + mThebes->CurrentMatrix(), + nsString(aBGColor), + renderDocFlags, flush, + nsIntSize(mWidth, mHeight)); + if (!pdocrender) + return NS_ERROR_FAILURE; + + DocumentRendererParent *docrender = + static_cast<DocumentRendererParent *>(pdocrender); + + docrender->SetCanvasContext(this, mThebes); + } +#endif +} + +// +// device pixel getting/setting +// + +void +CanvasRenderingContext2D::EnsureUnpremultiplyTable() { + if (sUnpremultiplyTable) + return; + + // Infallably alloc the unpremultiply table. + sUnpremultiplyTable = new uint8_t[256][256]; + + // It's important that the array be indexed first by alpha and then by rgb + // value. When we unpremultiply a pixel, we're guaranteed to do three + // lookups with the same alpha; indexing by alpha first makes it likely that + // those three lookups will be close to one another in memory, thus + // increasing the chance of a cache hit. + + // a == 0 case + for (uint32_t c = 0; c <= 255; c++) { + sUnpremultiplyTable[0][c] = c; + } + + for (int a = 1; a <= 255; a++) { + for (int c = 0; c <= 255; c++) { + sUnpremultiplyTable[a][c] = (uint8_t)((c * 255) / a); + } + } +} + + +already_AddRefed<ImageData> +CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, + double aSy, double aSw, + double aSh, ErrorResult& error) +{ + EnsureTarget(); + if (!IsTargetValid()) { + error.Throw(NS_ERROR_FAILURE); + return NULL; + } + + if (!mCanvasElement && !mDocShell) { + NS_ERROR("No canvas element and no docshell in GetImageData!!!"); + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return NULL; + } + + // Check only if we have a canvas element; if we were created with a docshell, + // then it's special internal use. + if (mCanvasElement && mCanvasElement->IsWriteOnly() && + !nsContentUtils::IsCallerChrome()) + { + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + error.Throw(NS_ERROR_DOM_SECURITY_ERR); + return NULL; + } + + if (!NS_finite(aSx) || !NS_finite(aSy) || + !NS_finite(aSw) || !NS_finite(aSh)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return NULL; + } + + if (!aSw || !aSh) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + int32_t x = JS_DoubleToInt32(aSx); + int32_t y = JS_DoubleToInt32(aSy); + int32_t wi = JS_DoubleToInt32(aSw); + int32_t hi = JS_DoubleToInt32(aSh); + + // Handle negative width and height by flipping the rectangle over in the + // relevant direction. + uint32_t w, h; + if (aSw < 0) { + w = -wi; + x -= w; + } else { + w = wi; + } + if (aSh < 0) { + h = -hi; + y -= h; + } else { + h = hi; + } + + if (w == 0) { + w = 1; + } + if (h == 0) { + h = 1; + } + + JSObject* array; + error = GetImageDataArray(aCx, x, y, w, h, &array); + if (error.Failed()) { + return NULL; + } + MOZ_ASSERT(array); + + nsRefPtr<ImageData> imageData = new ImageData(w, h, *array); + return imageData.forget(); +} + +nsresult +CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, + int32_t aX, + int32_t aY, + uint32_t aWidth, + uint32_t aHeight, + JSObject** aRetval) +{ + MOZ_ASSERT(aWidth && aHeight); + + CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; + if (!len.isValid()) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth; + CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight; + + if (!rightMost.isValid() || !bottomMost.isValid()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + JSObject* darray = JS_NewUint8ClampedArray(aCx, len.value()); + if (!darray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mZero) { + *aRetval = darray; + return NS_OK; + } + + uint8_t* data = JS_GetUint8ClampedArrayData(darray); + + IntRect srcRect(0, 0, mWidth, mHeight); + IntRect destRect(aX, aY, aWidth, aHeight); + + IntRect srcReadRect = srcRect.Intersect(destRect); + IntRect dstWriteRect = srcReadRect; + dstWriteRect.MoveBy(-aX, -aY); + + uint8_t* src = data; + uint32_t srcStride = aWidth * 4; + + RefPtr<DataSourceSurface> readback; + if (!srcReadRect.IsEmpty()) { + RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); + if (snapshot) { + readback = snapshot->GetDataSurface(); + + srcStride = readback->Stride(); + src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; + } + } + + // make sure sUnpremultiplyTable has been created + EnsureUnpremultiplyTable(); + + // NOTE! dst is the same as src, and this relies on reading + // from src and advancing that ptr before writing to dst. + // NOTE! I'm not sure that it is, I think this comment might have been + // inherited from Thebes canvas and is no longer true + uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; + + for (int32_t j = 0; j < dstWriteRect.height; ++j) { + for (int32_t i = 0; i < dstWriteRect.width; ++i) { + // XXX Is there some useful swizzle MMX we can use here? +#ifdef IS_LITTLE_ENDIAN + uint8_t b = *src++; + uint8_t g = *src++; + uint8_t r = *src++; + uint8_t a = *src++; +#else + uint8_t a = *src++; + uint8_t r = *src++; + uint8_t g = *src++; + uint8_t b = *src++; +#endif + // Convert to non-premultiplied color + *dst++ = sUnpremultiplyTable[a][r]; + *dst++ = sUnpremultiplyTable[a][g]; + *dst++ = sUnpremultiplyTable[a][b]; + *dst++ = a; + } + src += srcStride - (dstWriteRect.width * 4); + dst += (aWidth * 4) - (dstWriteRect.width * 4); + } + + *aRetval = darray; + return NS_OK; +} + +void +CanvasRenderingContext2D::EnsurePremultiplyTable() { + if (sPremultiplyTable) + return; + + // Infallably alloc the premultiply table. + sPremultiplyTable = new uint8_t[256][256]; + + // Like the unpremultiply table, it's important that we index the premultiply + // table with the alpha value as the first index to ensure good cache + // performance. + + for (int a = 0; a <= 255; a++) { + for (int c = 0; c <= 255; c++) { + sPremultiplyTable[a][c] = (a * c + 254) / 255; + } + } +} + +void +CanvasRenderingContext2D::EnsureErrorTarget() +{ + if (sErrorTarget) { + return; + } + + RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(IntSize(1, 1), FORMAT_B8G8R8A8); + NS_ABORT_IF_FALSE(errorTarget, "Failed to allocate the error target!"); + + sErrorTarget = errorTarget; + NS_ADDREF(sErrorTarget); +} + +void +CanvasRenderingContext2D::FillRuleChanged() +{ + if (mPath) { + mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); + mPath = nullptr; + } +} + +void +CanvasRenderingContext2D::PutImageData(JSContext* cx, + ImageData& imageData, double dx, + double dy, ErrorResult& error) +{ + if (!FloatValidate(dx, dy)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + dom::Uint8ClampedArray arr(imageData.GetDataObject()); + + error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), + imageData.Width(), imageData.Height(), + arr.Data(), arr.Length(), false, 0, 0, 0, 0); +} + +void +CanvasRenderingContext2D::PutImageData(JSContext* cx, + ImageData& imageData, double dx, + double dy, double dirtyX, + double dirtyY, double dirtyWidth, + double dirtyHeight, + ErrorResult& error) +{ + if (!FloatValidate(dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + dom::Uint8ClampedArray arr(imageData.GetDataObject()); + + error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), + imageData.Width(), imageData.Height(), + arr.Data(), arr.Length(), true, + JS_DoubleToInt32(dirtyX), + JS_DoubleToInt32(dirtyY), + JS_DoubleToInt32(dirtyWidth), + JS_DoubleToInt32(dirtyHeight)); +} + +// void putImageData (in ImageData d, in float x, in float y); +// void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight); + +nsresult +CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, + unsigned char *aData, uint32_t aDataLen, + bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, + int32_t dirtyWidth, int32_t dirtyHeight) +{ + if (w == 0 || h == 0) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + IntRect dirtyRect; + IntRect imageDataRect(0, 0, w, h); + + if (hasDirtyRect) { + // fix up negative dimensions + if (dirtyWidth < 0) { + NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); + + CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; + + if (!checkedDirtyX.isValid()) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + + dirtyX = checkedDirtyX.value(); + dirtyWidth = -dirtyWidth; + } + + if (dirtyHeight < 0) { + NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); + + CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; + + if (!checkedDirtyY.isValid()) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + + dirtyY = checkedDirtyY.value(); + dirtyHeight = -dirtyHeight; + } + + // bound the dirty rect within the imageData rectangle + dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); + + if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) + return NS_OK; + } else { + dirtyRect = imageDataRect; + } + + dirtyRect.MoveBy(IntPoint(x, y)); + dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); + + if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { + return NS_OK; + } + + uint32_t len = w * h * 4; + if (aDataLen != len) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(gfxIntSize(w, h), + gfxASurface::ImageFormatARGB32, + false); + if (!imgsurf || imgsurf->CairoStatus()) { + return NS_ERROR_FAILURE; + } + + // ensure premultiply table has been created + EnsurePremultiplyTable(); + + uint8_t *src = aData; + uint8_t *dst = imgsurf->Data(); + + for (uint32_t j = 0; j < h; j++) { + for (uint32_t i = 0; i < w; i++) { + uint8_t r = *src++; + uint8_t g = *src++; + uint8_t b = *src++; + uint8_t a = *src++; + + // Convert to premultiplied color (losslessly if the input came from getImageData) +#ifdef IS_LITTLE_ENDIAN + *dst++ = sPremultiplyTable[a][b]; + *dst++ = sPremultiplyTable[a][g]; + *dst++ = sPremultiplyTable[a][r]; + *dst++ = a; +#else + *dst++ = a; + *dst++ = sPremultiplyTable[a][r]; + *dst++ = sPremultiplyTable[a][g]; + *dst++ = sPremultiplyTable[a][b]; +#endif + } + } + + EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } + + RefPtr<SourceSurface> sourceSurface = + mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), FORMAT_B8G8R8A8); + + + mTarget->CopySurface(sourceSurface, + IntRect(dirtyRect.x - x, dirtyRect.y - y, + dirtyRect.width, dirtyRect.height), + IntPoint(dirtyRect.x, dirtyRect.y)); + + Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); + + return NS_OK; +} + +NS_IMETHODIMP +CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface) +{ + EnsureTarget(); + if (!mThebesSurface) { + mThebesSurface = + gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); + + if (!mThebesSurface) { + return NS_ERROR_FAILURE; + } + } else { + // Normally GetThebesSurfaceForDrawTarget will handle the flush, when + // we're returning a cached ThebesSurface we need to flush here. + mTarget->Flush(); + } + + *surface = mThebesSurface; + NS_ADDREF(*surface); + + return NS_OK; +} + +static already_AddRefed<ImageData> +CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, + uint32_t w, uint32_t h, ErrorResult& error) +{ + if (w == 0) + w = 1; + if (h == 0) + h = 1; + + CheckedInt<uint32_t> len = CheckedInt<uint32_t>(w) * h * 4; + if (!len.isValid()) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + // Create the fast typed array; it's initialized to 0 by default. + JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); + if (!darray) { + error.Throw(NS_ERROR_OUT_OF_MEMORY); + return NULL; + } + + nsRefPtr<mozilla::dom::ImageData> imageData = + new mozilla::dom::ImageData(w, h, *darray); + return imageData.forget(); +} + +already_AddRefed<ImageData> +CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, + double sh, ErrorResult& error) +{ + if (!FloatValidate(sw, sh)) { + error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return NULL; + } + + if (!sw || !sh) { + error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return NULL; + } + + int32_t wi = JS_DoubleToInt32(sw); + int32_t hi = JS_DoubleToInt32(sh); + + uint32_t w = NS_ABS(wi); + uint32_t h = NS_ABS(hi); + return mozilla::dom::CreateImageData(cx, this, w, h, error); +} + +already_AddRefed<ImageData> +CanvasRenderingContext2D::CreateImageData(JSContext* cx, + ImageData& imagedata, + ErrorResult& error) +{ + return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), + imagedata.Height(), error); +} + +static uint8_t g2DContextLayerUserData; + +already_AddRefed<CanvasLayer> +CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, + CanvasLayer *aOldLayer, + LayerManager *aManager) +{ + // Don't call EnsureTarget() ... if there isn't already a surface, then + // we have nothing to paint and there is no need to create a surface just + // t