Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/HTMLImageElement.h"
8 : #include "mozilla/dom/HTMLImageElementBinding.h"
9 : #include "nsGkAtoms.h"
10 : #include "nsStyleConsts.h"
11 : #include "nsPresContext.h"
12 : #include "nsMappedAttributes.h"
13 : #include "nsSize.h"
14 : #include "nsDocument.h"
15 : #include "nsIDocument.h"
16 : #include "nsIDOMMutationEvent.h"
17 : #include "nsIScriptContext.h"
18 : #include "nsIURL.h"
19 : #include "nsIIOService.h"
20 : #include "nsIServiceManager.h"
21 : #include "nsContentUtils.h"
22 : #include "nsContainerFrame.h"
23 : #include "nsNodeInfoManager.h"
24 : #include "mozilla/MouseEvents.h"
25 : #include "nsContentPolicyUtils.h"
26 : #include "nsIDOMWindow.h"
27 : #include "nsFocusManager.h"
28 : #include "mozilla/dom/HTMLFormElement.h"
29 : #include "nsAttrValueOrString.h"
30 : #include "imgLoader.h"
31 : #include "Image.h"
32 :
33 : // Responsive images!
34 : #include "mozilla/dom/HTMLSourceElement.h"
35 : #include "mozilla/dom/ResponsiveImageSelector.h"
36 :
37 : #include "imgIContainer.h"
38 : #include "imgILoader.h"
39 : #include "imgINotificationObserver.h"
40 : #include "imgRequestProxy.h"
41 :
42 : #include "nsILoadGroup.h"
43 :
44 : #include "nsIDOMHTMLMapElement.h"
45 : #include "mozilla/EventDispatcher.h"
46 : #include "mozilla/EventStates.h"
47 : #include "mozilla/GenericSpecifiedValuesInlines.h"
48 : #include "mozilla/net/ReferrerPolicy.h"
49 :
50 : #include "nsLayoutUtils.h"
51 :
52 : using namespace mozilla::net;
53 :
54 8 : NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
55 :
56 : #ifdef DEBUG
57 : // Is aSubject a previous sibling of aNode.
58 0 : static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
59 : {
60 0 : if (aSubject == aNode) {
61 0 : return false;
62 : }
63 :
64 0 : nsINode *parent = aSubject->GetParentNode();
65 0 : if (parent && parent == aNode->GetParentNode()) {
66 0 : return parent->IndexOf(aSubject) < parent->IndexOf(aNode);
67 : }
68 :
69 0 : return false;
70 : }
71 : #endif
72 :
73 : namespace mozilla {
74 : namespace dom {
75 :
76 : // Calls LoadSelectedImage on host element unless it has been superseded or
77 : // canceled -- this is the synchronous section of "update the image data".
78 : // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data
79 : class ImageLoadTask : public Runnable
80 : {
81 : public:
82 0 : ImageLoadTask(HTMLImageElement* aElement,
83 : bool aAlwaysLoad,
84 : bool aUseUrgentStartForChannel)
85 0 : : Runnable("dom::ImageLoadTask")
86 : , mElement(aElement)
87 : , mAlwaysLoad(aAlwaysLoad)
88 0 : , mUseUrgentStartForChannel(aUseUrgentStartForChannel)
89 : {
90 0 : mDocument = aElement->OwnerDoc();
91 0 : mDocument->BlockOnload();
92 0 : }
93 :
94 0 : NS_IMETHOD Run() override
95 : {
96 0 : if (mElement->mPendingImageLoadTask == this) {
97 0 : mElement->mPendingImageLoadTask = nullptr;
98 0 : mElement->mUseUrgentStartForChannel = mUseUrgentStartForChannel;
99 0 : mElement->LoadSelectedImage(true, true, mAlwaysLoad);
100 : }
101 0 : mDocument->UnblockOnload(false);
102 0 : return NS_OK;
103 : }
104 :
105 0 : bool AlwaysLoad() {
106 0 : return mAlwaysLoad;
107 : }
108 :
109 : private:
110 0 : ~ImageLoadTask() {}
111 : RefPtr<HTMLImageElement> mElement;
112 : nsCOMPtr<nsIDocument> mDocument;
113 : bool mAlwaysLoad;
114 :
115 : // True if we want to set nsIClassOfService::UrgentStart to the channel to
116 : // get the response ASAP for better user responsiveness.
117 : bool mUseUrgentStartForChannel;
118 : };
119 :
120 4 : HTMLImageElement::HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
121 : : nsGenericHTMLElement(aNodeInfo)
122 : , mForm(nullptr)
123 : , mInDocResponsiveContent(false)
124 4 : , mCurrentDensity(1.0)
125 : {
126 : // We start out broken
127 4 : AddStatesSilently(NS_EVENT_STATE_BROKEN);
128 4 : }
129 :
130 0 : HTMLImageElement::~HTMLImageElement()
131 : {
132 0 : DestroyImageLoadingContent();
133 0 : }
134 :
135 :
136 339 : NS_IMPL_ADDREF_INHERITED(HTMLImageElement, Element)
137 335 : NS_IMPL_RELEASE_INHERITED(HTMLImageElement, Element)
138 :
139 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement,
140 : nsGenericHTMLElement,
141 : mResponsiveSelector)
142 :
143 : // QueryInterface implementation for HTMLImageElement
144 184 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLImageElement)
145 180 : NS_INTERFACE_TABLE_INHERITED(HTMLImageElement,
146 : nsIDOMHTMLImageElement,
147 : nsIImageLoadingContent,
148 : imgIOnloadBlocker,
149 : imgINotificationObserver)
150 180 : NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
151 :
152 :
153 0 : NS_IMPL_ELEMENT_CLONE(HTMLImageElement)
154 :
155 :
156 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Name, name)
157 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Align, align)
158 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Alt, alt)
159 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Border, border)
160 0 : NS_IMPL_INT_ATTR(HTMLImageElement, Hspace, hspace)
161 0 : NS_IMPL_BOOL_ATTR(HTMLImageElement, IsMap, ismap)
162 0 : NS_IMPL_URI_ATTR(HTMLImageElement, LongDesc, longdesc)
163 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Sizes, sizes)
164 0 : NS_IMPL_URI_ATTR(HTMLImageElement, Lowsrc, lowsrc)
165 0 : NS_IMPL_URI_ATTR(HTMLImageElement, Src, src)
166 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, Srcset, srcset)
167 0 : NS_IMPL_STRING_ATTR(HTMLImageElement, UseMap, usemap)
168 0 : NS_IMPL_INT_ATTR(HTMLImageElement, Vspace, vspace)
169 :
170 : bool
171 0 : HTMLImageElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
172 : {
173 0 : return HasAttr(kNameSpaceID_None, nsGkAtoms::usemap) ||
174 0 : nsGenericHTMLElement::IsInteractiveHTMLContent(aIgnoreTabindex);
175 : }
176 :
177 : void
178 12 : HTMLImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
179 : {
180 12 : nsImageLoadingContent::AsyncEventRunning(aEvent);
181 12 : }
182 :
183 : nsresult
184 0 : HTMLImageElement::GetCurrentSrc(nsAString& aValue)
185 : {
186 0 : nsCOMPtr<nsIURI> currentURI;
187 0 : GetCurrentURI(getter_AddRefs(currentURI));
188 0 : if (currentURI) {
189 0 : nsAutoCString spec;
190 0 : currentURI->GetSpec(spec);
191 0 : CopyUTF8toUTF16(spec, aValue);
192 : } else {
193 0 : SetDOMStringToNull(aValue);
194 : }
195 :
196 0 : return NS_OK;
197 : }
198 :
199 : bool
200 0 : HTMLImageElement::Draggable() const
201 : {
202 : // images may be dragged unless the draggable attribute is false
203 0 : return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
204 0 : nsGkAtoms::_false, eIgnoreCase);
205 : }
206 :
207 : bool
208 0 : HTMLImageElement::Complete()
209 : {
210 0 : if (!mCurrentRequest) {
211 0 : return true;
212 : }
213 :
214 0 : if (mPendingRequest) {
215 0 : return false;
216 : }
217 :
218 : uint32_t status;
219 0 : mCurrentRequest->GetImageStatus(&status);
220 : return
221 0 : (status &
222 0 : (imgIRequest::STATUS_LOAD_COMPLETE | imgIRequest::STATUS_ERROR)) != 0;
223 : }
224 :
225 : NS_IMETHODIMP
226 0 : HTMLImageElement::GetComplete(bool* aComplete)
227 : {
228 0 : NS_PRECONDITION(aComplete, "Null out param!");
229 :
230 0 : *aComplete = Complete();
231 :
232 0 : return NS_OK;
233 : }
234 :
235 : CSSIntPoint
236 0 : HTMLImageElement::GetXY()
237 : {
238 0 : nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
239 0 : if (!frame) {
240 0 : return CSSIntPoint(0, 0);
241 : }
242 :
243 0 : nsIFrame* layer = nsLayoutUtils::GetClosestLayer(frame->GetParent());
244 0 : return CSSIntPoint::FromAppUnitsRounded(frame->GetOffsetTo(layer));
245 : }
246 :
247 : int32_t
248 0 : HTMLImageElement::X()
249 : {
250 0 : return GetXY().x;
251 : }
252 :
253 : int32_t
254 0 : HTMLImageElement::Y()
255 : {
256 0 : return GetXY().y;
257 : }
258 :
259 : NS_IMETHODIMP
260 0 : HTMLImageElement::GetX(int32_t* aX)
261 : {
262 0 : *aX = X();
263 0 : return NS_OK;
264 : }
265 :
266 : NS_IMETHODIMP
267 0 : HTMLImageElement::GetY(int32_t* aY)
268 : {
269 0 : *aY = Y();
270 0 : return NS_OK;
271 : }
272 :
273 : NS_IMETHODIMP
274 0 : HTMLImageElement::GetHeight(uint32_t* aHeight)
275 : {
276 0 : *aHeight = Height();
277 :
278 0 : return NS_OK;
279 : }
280 :
281 : NS_IMETHODIMP
282 0 : HTMLImageElement::SetHeight(uint32_t aHeight)
283 : {
284 0 : ErrorResult rv;
285 0 : SetHeight(aHeight, rv);
286 0 : return rv.StealNSResult();
287 : }
288 :
289 : NS_IMETHODIMP
290 0 : HTMLImageElement::GetWidth(uint32_t* aWidth)
291 : {
292 0 : *aWidth = Width();
293 :
294 0 : return NS_OK;
295 : }
296 :
297 : NS_IMETHODIMP
298 0 : HTMLImageElement::SetWidth(uint32_t aWidth)
299 : {
300 0 : ErrorResult rv;
301 0 : SetWidth(aWidth, rv);
302 0 : return rv.StealNSResult();
303 : }
304 :
305 : bool
306 4 : HTMLImageElement::ParseAttribute(int32_t aNamespaceID,
307 : nsIAtom* aAttribute,
308 : const nsAString& aValue,
309 : nsAttrValue& aResult)
310 : {
311 4 : if (aNamespaceID == kNameSpaceID_None) {
312 4 : if (aAttribute == nsGkAtoms::align) {
313 0 : return ParseAlignValue(aValue, aResult);
314 : }
315 4 : if (aAttribute == nsGkAtoms::crossorigin) {
316 0 : ParseCORSValue(aValue, aResult);
317 0 : return true;
318 : }
319 4 : if (ParseImageAttribute(aAttribute, aValue, aResult)) {
320 0 : return true;
321 : }
322 : }
323 :
324 4 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
325 4 : aResult);
326 : }
327 :
328 : void
329 0 : HTMLImageElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
330 : GenericSpecifiedValues* aData)
331 : {
332 0 : nsGenericHTMLElement::MapImageAlignAttributeInto(aAttributes, aData);
333 0 : nsGenericHTMLElement::MapImageBorderAttributeInto(aAttributes, aData);
334 0 : nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData);
335 0 : nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
336 0 : nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
337 0 : }
338 :
339 : nsChangeHint
340 0 : HTMLImageElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
341 : int32_t aModType) const
342 : {
343 : nsChangeHint retval =
344 0 : nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
345 0 : if (aAttribute == nsGkAtoms::usemap ||
346 0 : aAttribute == nsGkAtoms::ismap) {
347 0 : retval |= nsChangeHint_ReconstructFrame;
348 0 : } else if (aAttribute == nsGkAtoms::alt) {
349 0 : if (aModType == nsIDOMMutationEvent::ADDITION ||
350 : aModType == nsIDOMMutationEvent::REMOVAL) {
351 0 : retval |= nsChangeHint_ReconstructFrame;
352 : }
353 : }
354 0 : return retval;
355 : }
356 :
357 : NS_IMETHODIMP_(bool)
358 8 : HTMLImageElement::IsAttributeMapped(const nsIAtom* aAttribute) const
359 : {
360 : static const MappedAttributeEntry* const map[] = {
361 : sCommonAttributeMap,
362 : sImageMarginSizeAttributeMap,
363 : sImageBorderAttributeMap,
364 : sImageAlignAttributeMap
365 : };
366 :
367 8 : return FindAttributeDependence(aAttribute, map);
368 : }
369 :
370 :
371 : nsMapRuleToAttributesFunc
372 0 : HTMLImageElement::GetAttributeMappingFunction() const
373 : {
374 0 : return &MapAttributesIntoRule;
375 : }
376 :
377 : nsresult
378 8 : HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
379 : const nsAttrValueOrString* aValue,
380 : bool aNotify)
381 : {
382 8 : if (aNameSpaceID == kNameSpaceID_None && mForm &&
383 0 : (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
384 : // remove the image from the hashtable as needed
385 0 : nsAutoString tmp;
386 0 : GetAttr(kNameSpaceID_None, aName, tmp);
387 :
388 0 : if (!tmp.IsEmpty()) {
389 0 : mForm->RemoveImageElementFromTable(this, tmp);
390 : }
391 : }
392 :
393 8 : return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
394 8 : aValue, aNotify);
395 : }
396 :
397 : nsresult
398 8 : HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
399 : const nsAttrValue* aValue,
400 : const nsAttrValue* aOldValue, bool aNotify)
401 : {
402 16 : nsAttrValueOrString attrVal(aValue);
403 :
404 8 : if (aValue) {
405 8 : AfterMaybeChangeAttr(aNameSpaceID, aName, attrVal, aOldValue, true,
406 8 : aNotify);
407 : }
408 :
409 16 : if (aNameSpaceID == kNameSpaceID_None && mForm &&
410 0 : (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
411 8 : aValue && !aValue->IsEmptyString()) {
412 : // add the image to the hashtable as needed
413 0 : MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
414 : "Expected atom value for name/id");
415 0 : mForm->AddImageElementToTable(this,
416 0 : nsDependentAtomString(aValue->GetAtomValue()));
417 : }
418 :
419 : // Handle src/srcset updates. If aNotify is false, we are coming from the
420 : // parser or some such place; we'll get bound after all the attributes have
421 : // been set, so we'll do the image load from BindToTree.
422 :
423 8 : if (aName == nsGkAtoms::src &&
424 4 : aNameSpaceID == kNameSpaceID_None &&
425 : !aValue) {
426 : // Mark channel as urgent-start before load image if the image load is
427 : // initaiated by a user interaction.
428 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
429 :
430 : // SetAttr handles setting src since it needs to catch img.src =
431 : // img.src, so we only need to handle the unset case
432 0 : if (InResponsiveMode()) {
433 0 : if (mResponsiveSelector &&
434 0 : mResponsiveSelector->Content() == this) {
435 0 : mResponsiveSelector->SetDefaultSource(NullString());
436 : }
437 0 : QueueImageLoadTask(true);
438 : } else {
439 : // Bug 1076583 - We still behave synchronously in the non-responsive case
440 0 : CancelImageRequests(aNotify);
441 : }
442 8 : } else if (aName == nsGkAtoms::srcset &&
443 : aNameSpaceID == kNameSpaceID_None) {
444 : // Mark channel as urgent-start before load image if the image load is
445 : // initaiated by a user interaction.
446 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
447 :
448 0 : PictureSourceSrcsetChanged(this, attrVal.String(), aNotify);
449 8 : } else if (aName == nsGkAtoms::sizes &&
450 : aNameSpaceID == kNameSpaceID_None) {
451 : // Mark channel as urgent-start before load image if the image load is
452 : // initaiated by a user interaction.
453 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
454 :
455 0 : PictureSourceSizesChanged(this, attrVal.String(), aNotify);
456 : }
457 :
458 8 : return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
459 16 : aValue, aOldValue, aNotify);
460 : }
461 :
462 : nsresult
463 0 : HTMLImageElement::OnAttrSetButNotChanged(int32_t aNamespaceID, nsIAtom* aName,
464 : const nsAttrValueOrString& aValue,
465 : bool aNotify)
466 : {
467 0 : AfterMaybeChangeAttr(aNamespaceID, aName, aValue, nullptr, false, aNotify);
468 :
469 0 : return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
470 0 : aValue, aNotify);
471 : }
472 :
473 : void
474 8 : HTMLImageElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName,
475 : const nsAttrValueOrString& aValue,
476 : const nsAttrValue* aOldValue,
477 : bool aValueMaybeChanged, bool aNotify)
478 : {
479 8 : bool forceReload = false;
480 : // We need to force our image to reload. This must be done here, not in
481 : // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
482 : // being set to its existing value, which is normally optimized away as a
483 : // no-op.
484 : //
485 : // If we are in responsive mode, we drop the forced reload behavior,
486 : // but still trigger a image load task for img.src = img.src per
487 : // spec.
488 : //
489 : // Both cases handle unsetting src in AfterSetAttr
490 16 : if (aNamespaceID == kNameSpaceID_None &&
491 8 : aName == nsGkAtoms::src) {
492 :
493 : // Mark channel as urgent-start before load image if the image load is
494 : // initaiated by a user interaction.
495 4 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
496 :
497 8 : if (InResponsiveMode()) {
498 0 : if (mResponsiveSelector &&
499 0 : mResponsiveSelector->Content() == this) {
500 0 : mResponsiveSelector->SetDefaultSource(aValue.String());
501 : }
502 0 : QueueImageLoadTask(true);
503 4 : } else if (aNotify && OwnerDoc()->IsCurrentActiveDocument()) {
504 : // If aNotify is false, we are coming from the parser or some such place;
505 : // we'll get bound after all the attributes have been set, so we'll do the
506 : // sync image load from BindToTree. Skip the LoadImage call in that case.
507 :
508 : // Note that this sync behavior is partially removed from the spec, bug 1076583
509 :
510 : // A hack to get animations to reset. See bug 594771.
511 0 : mNewRequestsWillNeedAnimationReset = true;
512 :
513 : // Force image loading here, so that we'll try to load the image from
514 : // network if it's set to be not cacheable.
515 : // Potentially, false could be passed here rather than aNotify since
516 : // UpdateState will be called by SetAttrAndNotify, but there are two
517 : // obstacles to this: 1) LoadImage will end up calling
518 : // UpdateState(aNotify), and we do not want it to call UpdateState(false)
519 : // when aNotify is true, and 2) When this function is called by
520 : // OnAttrSetButNotChanged, SetAttrAndNotify will not subsequently call
521 : // UpdateState.
522 0 : LoadImage(aValue.String(), true, aNotify, eImageLoadType_Normal);
523 :
524 0 : mNewRequestsWillNeedAnimationReset = false;
525 : }
526 8 : } else if (aNamespaceID == kNameSpaceID_None &&
527 4 : aName == nsGkAtoms::crossorigin &&
528 : aNotify) {
529 0 : if (aValueMaybeChanged && GetCORSMode() != AttrValueToCORSMode(aOldValue)) {
530 : // Force a new load of the image with the new cross origin policy.
531 0 : forceReload = true;
532 : }
533 4 : } else if (aName == nsGkAtoms::referrerpolicy &&
534 0 : aNamespaceID == kNameSpaceID_None &&
535 : aNotify) {
536 0 : ReferrerPolicy referrerPolicy = GetImageReferrerPolicy();
537 0 : if (!InResponsiveMode() &&
538 0 : referrerPolicy != RP_Unset &&
539 0 : aValueMaybeChanged &&
540 0 : referrerPolicy != ReferrerPolicyFromAttr(aOldValue)) {
541 : // XXX: Bug 1076583 - We still use the older synchronous algorithm
542 : // Because referrerPolicy is not treated as relevant mutations, setting
543 : // the attribute will neither trigger a reload nor update the referrer
544 : // policy of the loading channel (whether it has previously completed or
545 : // not). Force a new load of the image with the new referrerpolicy.
546 0 : forceReload = true;
547 : }
548 : }
549 :
550 : // Because we load image synchronously in non-responsive-mode, we need to do
551 : // reload after the attribute has been set if the reload is triggerred by
552 : // cross origin changing.
553 8 : if (forceReload) {
554 : // Mark channel as urgent-start before load image if the image load is
555 : // initaiated by a user interaction.
556 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
557 :
558 0 : if (InResponsiveMode()) {
559 : // per spec, full selection runs when this changes, even though
560 : // it doesn't directly affect the source selection
561 0 : QueueImageLoadTask(true);
562 0 : } else if (OwnerDoc()->IsCurrentActiveDocument()) {
563 : // Bug 1076583 - We still use the older synchronous algorithm in
564 : // non-responsive mode. Force a new load of the image with the
565 : // new cross origin policy
566 0 : ForceReload(aNotify);
567 : }
568 : }
569 8 : }
570 :
571 : nsresult
572 12 : HTMLImageElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
573 : {
574 : // We handle image element with attribute ismap in its corresponding frame
575 : // element. Set mMultipleActionsPrevented here to prevent the click event
576 : // trigger the behaviors in Element::PostHandleEventForLinks
577 12 : WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
578 12 : if (mouseEvent && mouseEvent->IsLeftClickEvent() && IsMap()) {
579 0 : mouseEvent->mFlags.mMultipleActionsPrevented = true;
580 : }
581 12 : return nsGenericHTMLElement::GetEventTargetParent(aVisitor);
582 : }
583 :
584 : bool
585 0 : HTMLImageElement::IsHTMLFocusable(bool aWithMouse,
586 : bool *aIsFocusable, int32_t *aTabIndex)
587 : {
588 0 : int32_t tabIndex = TabIndex();
589 :
590 0 : if (IsInUncomposedDoc()) {
591 0 : nsAutoString usemap;
592 0 : GetUseMap(usemap);
593 : // XXXbz which document should this be using? sXBL/XBL2 issue! I
594 : // think that OwnerDoc() is right, since we don't want to
595 : // assume stuff about the document we're bound to.
596 0 : if (OwnerDoc()->FindImageMap(usemap)) {
597 0 : if (aTabIndex) {
598 : // Use tab index on individual map areas
599 0 : *aTabIndex = (sTabFocusModel & eTabFocus_linksMask)? 0 : -1;
600 : }
601 : // Image map is not focusable itself, but flag as tabbable
602 : // so that image map areas get walked into.
603 0 : *aIsFocusable = false;
604 :
605 0 : return false;
606 : }
607 : }
608 :
609 0 : if (aTabIndex) {
610 : // Can be in tab order if tabindex >=0 and form controls are tabbable.
611 0 : *aTabIndex = (sTabFocusModel & eTabFocus_formElementsMask)? tabIndex : -1;
612 : }
613 :
614 0 : *aIsFocusable =
615 : #ifdef XP_MACOSX
616 : (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
617 : #endif
618 0 : (tabIndex >= 0 || HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
619 :
620 0 : return false;
621 : }
622 :
623 : nsresult
624 4 : HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
625 : nsIContent* aBindingParent,
626 : bool aCompileEventHandlers)
627 : {
628 4 : nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
629 : aBindingParent,
630 4 : aCompileEventHandlers);
631 4 : NS_ENSURE_SUCCESS(rv, rv);
632 :
633 4 : nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
634 4 : aCompileEventHandlers);
635 :
636 4 : if (aParent) {
637 4 : UpdateFormOwner();
638 : }
639 :
640 4 : if (HaveSrcsetOrInPicture()) {
641 0 : if (aDocument && !mInDocResponsiveContent) {
642 0 : aDocument->AddResponsiveContent(this);
643 0 : mInDocResponsiveContent = true;
644 : }
645 :
646 : // Mark channel as urgent-start before load image if the image load is
647 : // initaiated by a user interaction.
648 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
649 :
650 : // Run selection algorithm when an img element is inserted into a document
651 : // in order to react to changes in the environment. See note of
652 : // https://html.spec.whatwg.org/multipage/embedded-content.html#img-environment-changes
653 0 : QueueImageLoadTask(false);
654 8 : } else if (!InResponsiveMode() &&
655 4 : HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
656 : // We skip loading when our attributes were set from parser land,
657 : // so trigger a aForce=false load now to check if things changed.
658 : // This isn't necessary for responsive mode, since creating the
659 : // image load task is asynchronous we don't need to take special
660 : // care to avoid doing so when being filled by the parser.
661 :
662 : // Mark channel as urgent-start before load image if the image load is
663 : // initaiated by a user interaction.
664 4 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
665 :
666 : // FIXME: Bug 660963 it would be nice if we could just have
667 : // ClearBrokenState update our state and do it fast...
668 4 : ClearBrokenState();
669 4 : RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
670 :
671 : // We still act synchronously for the non-responsive case (Bug
672 : // 1076583), but still need to delay if it is unsafe to run
673 : // script.
674 :
675 : // If loading is temporarily disabled, don't even launch MaybeLoadImage.
676 : // Otherwise MaybeLoadImage may run later when someone has reenabled
677 : // loading.
678 8 : if (LoadingEnabled() &&
679 4 : OwnerDoc()->IsCurrentActiveDocument()) {
680 8 : nsContentUtils::AddScriptRunner(
681 8 : NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage",
682 : this,
683 : &HTMLImageElement::MaybeLoadImage,
684 4 : false));
685 : }
686 : }
687 :
688 4 : return rv;
689 : }
690 :
691 : void
692 0 : HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
693 : {
694 0 : if (mForm) {
695 0 : if (aNullParent || !FindAncestorForm(mForm)) {
696 0 : ClearForm(true);
697 : } else {
698 0 : UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
699 : }
700 : }
701 :
702 0 : if (mInDocResponsiveContent) {
703 0 : nsIDocument* doc = GetOurOwnerDoc();
704 0 : MOZ_ASSERT(doc);
705 0 : if (doc) {
706 0 : doc->RemoveResponsiveContent(this);
707 0 : mInDocResponsiveContent = false;
708 : }
709 : }
710 :
711 0 : nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
712 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
713 0 : }
714 :
715 : void
716 4 : HTMLImageElement::UpdateFormOwner()
717 : {
718 4 : if (!mForm) {
719 4 : mForm = FindAncestorForm();
720 : }
721 :
722 4 : if (mForm && !HasFlag(ADDED_TO_FORM)) {
723 : // Now we need to add ourselves to the form
724 0 : nsAutoString nameVal, idVal;
725 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
726 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
727 :
728 0 : SetFlags(ADDED_TO_FORM);
729 :
730 0 : mForm->AddImageElement(this);
731 :
732 0 : if (!nameVal.IsEmpty()) {
733 0 : mForm->AddImageElementToTable(this, nameVal);
734 : }
735 :
736 0 : if (!idVal.IsEmpty()) {
737 0 : mForm->AddImageElementToTable(this, idVal);
738 : }
739 : }
740 4 : }
741 :
742 : void
743 4 : HTMLImageElement::MaybeLoadImage(bool aAlwaysForceLoad)
744 : {
745 : // Our base URI may have changed, or we may have had responsive parameters
746 : // change while not bound to the tree. Re-parse src/srcset and call LoadImage,
747 : // which is a no-op if it resolves to the same effective URI without aForce.
748 :
749 : // Note, check LoadingEnabled() after LoadImage call.
750 :
751 4 : LoadSelectedImage(aAlwaysForceLoad, /* aNotify */ true, aAlwaysForceLoad);
752 :
753 4 : if (!LoadingEnabled()) {
754 0 : CancelImageRequests(true);
755 : }
756 4 : }
757 :
758 : EventStates
759 24 : HTMLImageElement::IntrinsicState() const
760 : {
761 48 : return nsGenericHTMLElement::IntrinsicState() |
762 72 : nsImageLoadingContent::ImageState();
763 : }
764 :
765 : void
766 0 : HTMLImageElement::NodeInfoChanged(nsIDocument* aOldDoc)
767 : {
768 0 : nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
769 : // Force reload image if adoption steps are run.
770 : // If loading is temporarily disabled, don't even launch script runner.
771 : // Otherwise script runner may run later when someone has reenabled loading.
772 0 : if (LoadingEnabled()) {
773 : // Use script runner for the case the adopt is from appendChild.
774 : // Bug 1076583 - We still behave synchronously in the non-responsive case
775 0 : nsContentUtils::AddScriptRunner(
776 0 : (InResponsiveMode())
777 0 : ? NewRunnableMethod<bool>("dom::HTMLImageElement::QueueImageLoadTask",
778 : this,
779 : &HTMLImageElement::QueueImageLoadTask,
780 : true)
781 : : NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage",
782 : this,
783 : &HTMLImageElement::MaybeLoadImage,
784 0 : true));
785 : }
786 0 : }
787 :
788 : // static
789 : already_AddRefed<HTMLImageElement>
790 0 : HTMLImageElement::Image(const GlobalObject& aGlobal,
791 : const Optional<uint32_t>& aWidth,
792 : const Optional<uint32_t>& aHeight,
793 : ErrorResult& aError)
794 : {
795 0 : nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
796 : nsIDocument* doc;
797 0 : if (!win || !(doc = win->GetExtantDoc())) {
798 0 : aError.Throw(NS_ERROR_FAILURE);
799 0 : return nullptr;
800 : }
801 :
802 : already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
803 : doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::img, nullptr,
804 : kNameSpaceID_XHTML,
805 0 : nsIDOMNode::ELEMENT_NODE);
806 :
807 0 : RefPtr<HTMLImageElement> img = new HTMLImageElement(nodeInfo);
808 :
809 0 : if (aWidth.WasPassed()) {
810 0 : img->SetWidth(aWidth.Value(), aError);
811 0 : if (aError.Failed()) {
812 0 : return nullptr;
813 : }
814 :
815 0 : if (aHeight.WasPassed()) {
816 0 : img->SetHeight(aHeight.Value(), aError);
817 0 : if (aError.Failed()) {
818 0 : return nullptr;
819 : }
820 : }
821 : }
822 :
823 0 : return img.forget();
824 : }
825 :
826 : uint32_t
827 0 : HTMLImageElement::NaturalHeight()
828 : {
829 : uint32_t height;
830 0 : nsresult rv = nsImageLoadingContent::GetNaturalHeight(&height);
831 :
832 0 : if (NS_FAILED(rv)) {
833 0 : MOZ_ASSERT(false, "GetNaturalHeight should not fail");
834 : return 0;
835 : }
836 :
837 0 : if (mResponsiveSelector) {
838 0 : double density = mResponsiveSelector->GetSelectedImageDensity();
839 0 : MOZ_ASSERT(density >= 0.0);
840 0 : height = NSToIntRound(double(height) / density);
841 : }
842 :
843 0 : return height;
844 : }
845 :
846 : NS_IMETHODIMP
847 0 : HTMLImageElement::GetNaturalHeight(uint32_t* aNaturalHeight)
848 : {
849 0 : *aNaturalHeight = NaturalHeight();
850 0 : return NS_OK;
851 : }
852 :
853 : uint32_t
854 0 : HTMLImageElement::NaturalWidth()
855 : {
856 : uint32_t width;
857 0 : nsresult rv = nsImageLoadingContent::GetNaturalWidth(&width);
858 :
859 0 : if (NS_FAILED(rv)) {
860 0 : MOZ_ASSERT(false, "GetNaturalWidth should not fail");
861 : return 0;
862 : }
863 :
864 0 : if (mResponsiveSelector) {
865 0 : double density = mResponsiveSelector->GetSelectedImageDensity();
866 0 : MOZ_ASSERT(density >= 0.0);
867 0 : width = NSToIntRound(double(width) / density);
868 : }
869 :
870 0 : return width;
871 : }
872 :
873 : NS_IMETHODIMP
874 0 : HTMLImageElement::GetNaturalWidth(uint32_t* aNaturalWidth)
875 : {
876 0 : *aNaturalWidth = NaturalWidth();
877 0 : return NS_OK;
878 : }
879 :
880 : nsresult
881 0 : HTMLImageElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren)
882 : {
883 0 : bool destIsStatic = aDest->OwnerDoc()->IsStaticDocument();
884 0 : auto dest = static_cast<HTMLImageElement*>(aDest);
885 0 : if (destIsStatic) {
886 0 : CreateStaticImageClone(dest);
887 : }
888 :
889 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest, aPreallocateChildren);
890 0 : if (NS_FAILED(rv)) {
891 0 : return rv;
892 : }
893 :
894 0 : if (!destIsStatic) {
895 : // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), dest skipped
896 : // doing the image load because we passed in false for aNotify. But we
897 : // really do want it to do the load, so set it up to happen once the cloning
898 : // reaches a stable state.
899 0 : if (!dest->InResponsiveMode() &&
900 0 : dest->HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
901 0 : dest->OwnerDoc()->IsCurrentActiveDocument()) {
902 : // Mark channel as urgent-start before load image if the image load is
903 : // initaiated by a user interaction.
904 0 : mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
905 :
906 0 : nsContentUtils::AddScriptRunner(
907 0 : NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage",
908 : dest,
909 : &HTMLImageElement::MaybeLoadImage,
910 0 : false));
911 : }
912 : }
913 :
914 0 : return NS_OK;
915 : }
916 :
917 : CORSMode
918 4 : HTMLImageElement::GetCORSMode()
919 : {
920 4 : return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
921 : }
922 :
923 : JSObject*
924 0 : HTMLImageElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
925 : {
926 0 : return HTMLImageElementBinding::Wrap(aCx, this, aGivenProto);
927 : }
928 :
929 : #ifdef DEBUG
930 : nsIDOMHTMLFormElement*
931 0 : HTMLImageElement::GetForm() const
932 : {
933 0 : return mForm;
934 : }
935 : #endif
936 :
937 : void
938 0 : HTMLImageElement::SetForm(nsIDOMHTMLFormElement* aForm)
939 : {
940 0 : NS_PRECONDITION(aForm, "Don't pass null here");
941 0 : NS_ASSERTION(!mForm,
942 : "We don't support switching from one non-null form to another.");
943 :
944 0 : mForm = static_cast<HTMLFormElement*>(aForm);
945 0 : }
946 :
947 : void
948 0 : HTMLImageElement::ClearForm(bool aRemoveFromForm)
949 : {
950 0 : NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
951 : "Form control should have had flag set correctly");
952 :
953 0 : if (!mForm) {
954 0 : return;
955 : }
956 :
957 0 : if (aRemoveFromForm) {
958 0 : nsAutoString nameVal, idVal;
959 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
960 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
961 :
962 0 : mForm->RemoveImageElement(this);
963 :
964 0 : if (!nameVal.IsEmpty()) {
965 0 : mForm->RemoveImageElementFromTable(this, nameVal);
966 : }
967 :
968 0 : if (!idVal.IsEmpty()) {
969 0 : mForm->RemoveImageElementFromTable(this, idVal);
970 : }
971 : }
972 :
973 0 : UnsetFlags(ADDED_TO_FORM);
974 0 : mForm = nullptr;
975 : }
976 :
977 : void
978 0 : HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad)
979 : {
980 : // If loading is temporarily disabled, we don't want to queue tasks
981 : // that may then run when loading is re-enabled.
982 0 : if (!LoadingEnabled() || !this->OwnerDoc()->IsCurrentActiveDocument()) {
983 0 : return;
984 : }
985 :
986 : // Ensure that we don't overwrite a previous load request that requires
987 : // a complete load to occur.
988 0 : bool alwaysLoad = aAlwaysLoad;
989 0 : if (mPendingImageLoadTask) {
990 0 : alwaysLoad = alwaysLoad || mPendingImageLoadTask->AlwaysLoad();
991 : }
992 : RefPtr<ImageLoadTask> task = new ImageLoadTask(this,
993 : alwaysLoad,
994 0 : mUseUrgentStartForChannel);
995 : // The task checks this to determine if it was the last
996 : // queued event, and so earlier tasks are implicitly canceled.
997 0 : mPendingImageLoadTask = task;
998 0 : nsContentUtils::RunInStableState(task.forget());
999 : }
1000 :
1001 : bool
1002 16 : HTMLImageElement::HaveSrcsetOrInPicture()
1003 : {
1004 16 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::srcset)) {
1005 0 : return true;
1006 : }
1007 :
1008 16 : Element *parent = nsINode::GetParentElement();
1009 16 : return (parent && parent->IsHTMLElement(nsGkAtoms::picture));
1010 : }
1011 :
1012 : bool
1013 8 : HTMLImageElement::InResponsiveMode()
1014 : {
1015 : // When we lose srcset or leave a <picture> element, the fallback to img.src
1016 : // will happen from the microtask, and we should behave responsively in the
1017 : // interim
1018 8 : return mResponsiveSelector ||
1019 16 : mPendingImageLoadTask ||
1020 16 : HaveSrcsetOrInPicture();
1021 : }
1022 :
1023 : bool
1024 4 : HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource, double aSelectedDensity)
1025 : {
1026 : // If there was no selected source previously, we don't want to short-circuit the load.
1027 : // Similarly for if there is no newly selected source.
1028 4 : if (!mLastSelectedSource || !aSelectedSource) {
1029 4 : return false;
1030 : }
1031 0 : bool equal = false;
1032 0 : return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) && equal &&
1033 0 : aSelectedDensity == mCurrentDensity;
1034 : }
1035 :
1036 : nsresult
1037 4 : HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad)
1038 : {
1039 4 : nsresult rv = NS_ERROR_FAILURE;
1040 :
1041 4 : if (aForce) {
1042 : // In responsive mode we generally want to re-run the full
1043 : // selection algorithm whenever starting a new load, per
1044 : // spec. This also causes us to re-resolve the URI as appropriate.
1045 0 : if (!UpdateResponsiveSource() && !aAlwaysLoad) {
1046 0 : return NS_OK;
1047 : }
1048 : }
1049 :
1050 8 : nsCOMPtr<nsIURI> selectedSource;
1051 4 : double currentDensity = 1.0; // default to 1.0 for the src attribute case
1052 4 : if (mResponsiveSelector) {
1053 0 : nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
1054 0 : selectedSource = url;
1055 0 : currentDensity = mResponsiveSelector->GetSelectedImageDensity();
1056 0 : if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
1057 0 : return NS_OK;
1058 : }
1059 0 : if (url) {
1060 0 : rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset);
1061 : }
1062 : } else {
1063 8 : nsAutoString src;
1064 4 : if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
1065 0 : CancelImageRequests(aNotify);
1066 0 : rv = NS_OK;
1067 : } else {
1068 4 : nsIDocument* doc = GetOurOwnerDoc();
1069 4 : if (doc) {
1070 4 : StringToURI(src, doc, getter_AddRefs(selectedSource));
1071 4 : if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
1072 0 : return NS_OK;
1073 : }
1074 : }
1075 :
1076 : // If we have a srcset attribute or are in a <picture> element,
1077 : // we always use the Imageset load type, even if we parsed no
1078 : // valid responsive sources from either, per spec.
1079 4 : rv = LoadImage(src, aForce, aNotify,
1080 4 : HaveSrcsetOrInPicture() ? eImageLoadType_Imageset
1081 4 : : eImageLoadType_Normal);
1082 : }
1083 : }
1084 4 : mLastSelectedSource = selectedSource;
1085 4 : mCurrentDensity = currentDensity;
1086 :
1087 4 : if (NS_FAILED(rv)) {
1088 0 : CancelImageRequests(aNotify);
1089 : }
1090 4 : return rv;
1091 : }
1092 :
1093 : void
1094 0 : HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode,
1095 : const nsAString& aNewValue,
1096 : bool aNotify)
1097 : {
1098 0 : MOZ_ASSERT(aSourceNode == this ||
1099 : IsPreviousSibling(aSourceNode, this),
1100 : "Should not be getting notifications for non-previous-siblings");
1101 :
1102 : nsIContent *currentSrc =
1103 0 : mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
1104 :
1105 0 : if (aSourceNode == currentSrc) {
1106 : // We're currently using this node as our responsive selector
1107 : // source.
1108 0 : mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
1109 : }
1110 :
1111 0 : if (!mInDocResponsiveContent && IsInComposedDoc()) {
1112 0 : nsIDocument* doc = GetOurOwnerDoc();
1113 0 : if (doc) {
1114 0 : doc->AddResponsiveContent(this);
1115 0 : mInDocResponsiveContent = true;
1116 : }
1117 : }
1118 :
1119 : // This always triggers the image update steps per the spec, even if
1120 : // we are not using this source.
1121 0 : QueueImageLoadTask(true);
1122 0 : }
1123 :
1124 : void
1125 0 : HTMLImageElement::PictureSourceSizesChanged(nsIContent *aSourceNode,
1126 : const nsAString& aNewValue,
1127 : bool aNotify)
1128 : {
1129 0 : MOZ_ASSERT(aSourceNode == this ||
1130 : IsPreviousSibling(aSourceNode, this),
1131 : "Should not be getting notifications for non-previous-siblings");
1132 :
1133 : nsIContent *currentSrc =
1134 0 : mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
1135 :
1136 0 : if (aSourceNode == currentSrc) {
1137 : // We're currently using this node as our responsive selector
1138 : // source.
1139 0 : mResponsiveSelector->SetSizesFromDescriptor(aNewValue);
1140 : }
1141 :
1142 : // This always triggers the image update steps per the spec, even if
1143 : // we are not using this source.
1144 0 : QueueImageLoadTask(true);
1145 0 : }
1146 :
1147 : void
1148 0 : HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode,
1149 : bool aNotify)
1150 : {
1151 0 : MOZ_ASSERT(IsPreviousSibling(aSourceNode, this),
1152 : "Should not be getting notifications for non-previous-siblings");
1153 :
1154 : // This always triggers the image update steps per the spec, even if
1155 : // we are not switching to/from this source
1156 0 : QueueImageLoadTask(true);
1157 0 : }
1158 :
1159 : void
1160 0 : HTMLImageElement::PictureSourceAdded(nsIContent *aSourceNode)
1161 : {
1162 0 : MOZ_ASSERT(aSourceNode == this ||
1163 : IsPreviousSibling(aSourceNode, this),
1164 : "Should not be getting notifications for non-previous-siblings");
1165 :
1166 0 : QueueImageLoadTask(true);
1167 0 : }
1168 :
1169 : void
1170 0 : HTMLImageElement::PictureSourceRemoved(nsIContent *aSourceNode)
1171 : {
1172 0 : MOZ_ASSERT(aSourceNode == this ||
1173 : IsPreviousSibling(aSourceNode, this),
1174 : "Should not be getting notifications for non-previous-siblings");
1175 :
1176 0 : QueueImageLoadTask(true);
1177 0 : }
1178 :
1179 : bool
1180 0 : HTMLImageElement::UpdateResponsiveSource()
1181 : {
1182 0 : bool hadSelector = !!mResponsiveSelector;
1183 :
1184 : nsIContent *currentSource =
1185 0 : mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
1186 0 : Element *parent = nsINode::GetParentElement();
1187 :
1188 0 : nsINode *candidateSource = nullptr;
1189 0 : if (parent && parent->IsHTMLElement(nsGkAtoms::picture)) {
1190 : // Walk source nodes previous to ourselves
1191 0 : candidateSource = parent->GetFirstChild();
1192 : } else {
1193 0 : candidateSource = this;
1194 : }
1195 :
1196 0 : while (candidateSource) {
1197 0 : if (candidateSource == currentSource) {
1198 : // found no better source before current, re-run selection on
1199 : // that and keep it if it's still usable.
1200 0 : bool changed = mResponsiveSelector->SelectImage(true);
1201 0 : if (mResponsiveSelector->NumCandidates()) {
1202 0 : bool isUsableCandidate = true;
1203 :
1204 : // an otherwise-usable source element may still have a media query that may not
1205 : // match any more.
1206 0 : if (candidateSource->IsHTMLElement(nsGkAtoms::source) &&
1207 0 : !SourceElementMatches(candidateSource->AsContent())) {
1208 0 : isUsableCandidate = false;
1209 : }
1210 :
1211 0 : if (isUsableCandidate) {
1212 0 : return changed;
1213 : }
1214 : }
1215 :
1216 : // no longer valid
1217 0 : mResponsiveSelector = nullptr;
1218 0 : if (candidateSource == this) {
1219 : // No further possibilities
1220 0 : break;
1221 : }
1222 0 : } else if (candidateSource == this) {
1223 : // We are the last possible source
1224 0 : if (!TryCreateResponsiveSelector(candidateSource->AsContent())) {
1225 : // Failed to find any source
1226 0 : mResponsiveSelector = nullptr;
1227 : }
1228 0 : break;
1229 0 : } else if (candidateSource->IsHTMLElement(nsGkAtoms::source) &&
1230 0 : TryCreateResponsiveSelector(candidateSource->AsContent())) {
1231 : // This led to a valid source, stop
1232 0 : break;
1233 : }
1234 0 : candidateSource = candidateSource->GetNextSibling();
1235 : }
1236 :
1237 0 : if (!candidateSource) {
1238 : // Ran out of siblings without finding ourself, e.g. XBL magic.
1239 0 : mResponsiveSelector = nullptr;
1240 : }
1241 :
1242 : // If we reach this point, either:
1243 : // - there was no selector originally, and there is not one now
1244 : // - there was no selector originally, and there is one now
1245 : // - there was a selector, and there is a different one now
1246 : // - there was a selector, and there is not one now
1247 0 : return hadSelector || mResponsiveSelector;
1248 : }
1249 :
1250 : /*static */ bool
1251 0 : HTMLImageElement::SupportedPictureSourceType(const nsAString& aType)
1252 : {
1253 0 : nsAutoString type;
1254 0 : nsAutoString params;
1255 :
1256 0 : nsContentUtils::SplitMimeType(aType, type, params);
1257 0 : if (type.IsEmpty()) {
1258 0 : return true;
1259 : }
1260 :
1261 : return
1262 0 : imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get(),
1263 0 : AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
1264 : }
1265 :
1266 : bool
1267 0 : HTMLImageElement::SourceElementMatches(nsIContent* aSourceNode)
1268 : {
1269 0 : MOZ_ASSERT(aSourceNode->IsHTMLElement(nsGkAtoms::source));
1270 :
1271 0 : DebugOnly<Element *> parent(nsINode::GetParentElement());
1272 0 : MOZ_ASSERT(parent && parent->IsHTMLElement(nsGkAtoms::picture));
1273 0 : MOZ_ASSERT(IsPreviousSibling(aSourceNode, this));
1274 :
1275 : // Check media and type
1276 0 : HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode);
1277 0 : if (!src->MatchesCurrentMedia()) {
1278 0 : return false;
1279 : }
1280 :
1281 0 : nsAutoString type;
1282 0 : if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
1283 0 : !SupportedPictureSourceType(type)) {
1284 0 : return false;
1285 : }
1286 :
1287 0 : return true;
1288 : }
1289 :
1290 : bool
1291 0 : HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode)
1292 : {
1293 : // Skip if this is not a <source> with matching media query
1294 0 : bool isSourceTag = aSourceNode->IsHTMLElement(nsGkAtoms::source);
1295 0 : if (isSourceTag) {
1296 0 : if (!SourceElementMatches(aSourceNode)) {
1297 0 : return false;
1298 : }
1299 0 : } else if (aSourceNode->IsHTMLElement(nsGkAtoms::img)) {
1300 : // Otherwise this is the <img> tag itself
1301 0 : MOZ_ASSERT(aSourceNode == this);
1302 : }
1303 :
1304 : // Skip if has no srcset or an empty srcset
1305 0 : nsString srcset;
1306 0 : if (!aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset, srcset)) {
1307 0 : return false;
1308 : }
1309 :
1310 0 : if (srcset.IsEmpty()) {
1311 0 : return false;
1312 : }
1313 :
1314 :
1315 : // Try to parse
1316 0 : RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aSourceNode);
1317 0 : if (!sel->SetCandidatesFromSourceSet(srcset)) {
1318 : // No possible candidates, don't need to bother parsing sizes
1319 0 : return false;
1320 : }
1321 :
1322 0 : nsAutoString sizes;
1323 0 : aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
1324 0 : sel->SetSizesFromDescriptor(sizes);
1325 :
1326 : // If this is the <img> tag, also pull in src as the default source
1327 0 : if (!isSourceTag) {
1328 0 : MOZ_ASSERT(aSourceNode == this);
1329 0 : nsAutoString src;
1330 0 : if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
1331 0 : sel->SetDefaultSource(src);
1332 : }
1333 : }
1334 :
1335 0 : mResponsiveSelector = sel;
1336 0 : return true;
1337 : }
1338 :
1339 : /* static */ bool
1340 0 : HTMLImageElement::SelectSourceForTagWithAttrs(nsIDocument *aDocument,
1341 : bool aIsSourceTag,
1342 : const nsAString& aSrcAttr,
1343 : const nsAString& aSrcsetAttr,
1344 : const nsAString& aSizesAttr,
1345 : const nsAString& aTypeAttr,
1346 : const nsAString& aMediaAttr,
1347 : nsAString& aResult)
1348 : {
1349 0 : MOZ_ASSERT(aIsSourceTag || (aTypeAttr.IsEmpty() && aMediaAttr.IsEmpty()),
1350 : "Passing type or media attrs makes no sense without aIsSourceTag");
1351 0 : MOZ_ASSERT(!aIsSourceTag || aSrcAttr.IsEmpty(),
1352 : "Passing aSrcAttr makes no sense with aIsSourceTag set");
1353 :
1354 0 : if (aSrcsetAttr.IsEmpty()) {
1355 0 : if (!aIsSourceTag) {
1356 : // For an <img> with no srcset, we would always select the src attr.
1357 0 : aResult.Assign(aSrcAttr);
1358 0 : return true;
1359 : }
1360 : // Otherwise, a <source> without srcset is never selected
1361 0 : return false;
1362 : }
1363 :
1364 : // Would not consider source tags with unsupported media or type
1365 0 : if (aIsSourceTag &&
1366 0 : ((!aMediaAttr.IsVoid() &&
1367 0 : !HTMLSourceElement::WouldMatchMediaForDocument(aMediaAttr, aDocument)) ||
1368 0 : (!aTypeAttr.IsVoid() &&
1369 0 : !SupportedPictureSourceType(aTypeAttr)))) {
1370 0 : return false;
1371 : }
1372 :
1373 : // Using srcset or picture <source>, build a responsive selector for this tag.
1374 : RefPtr<ResponsiveImageSelector> sel =
1375 0 : new ResponsiveImageSelector(aDocument);
1376 :
1377 0 : sel->SetCandidatesFromSourceSet(aSrcsetAttr);
1378 0 : if (!aSizesAttr.IsEmpty()) {
1379 0 : sel->SetSizesFromDescriptor(aSizesAttr);
1380 : }
1381 0 : if (!aIsSourceTag) {
1382 0 : sel->SetDefaultSource(aSrcAttr);
1383 : }
1384 :
1385 0 : if (sel->GetSelectedImageURLSpec(aResult)) {
1386 0 : return true;
1387 : }
1388 :
1389 0 : if (!aIsSourceTag) {
1390 : // <img> tag with no match would definitively load nothing.
1391 0 : aResult.Truncate();
1392 0 : return true;
1393 : }
1394 :
1395 : // <source> tags with no match would leave source yet-undetermined.
1396 0 : return false;
1397 : }
1398 :
1399 : void
1400 0 : HTMLImageElement::DestroyContent()
1401 : {
1402 0 : mResponsiveSelector = nullptr;
1403 :
1404 0 : nsGenericHTMLElement::DestroyContent();
1405 0 : }
1406 :
1407 : void
1408 0 : HTMLImageElement::MediaFeatureValuesChanged()
1409 : {
1410 0 : QueueImageLoadTask(false);
1411 0 : }
1412 :
1413 : void
1414 0 : HTMLImageElement::FlushUseCounters()
1415 : {
1416 0 : nsCOMPtr<imgIRequest> request;
1417 0 : GetRequest(CURRENT_REQUEST, getter_AddRefs(request));
1418 :
1419 0 : nsCOMPtr<imgIContainer> container;
1420 0 : request->GetImage(getter_AddRefs(container));
1421 :
1422 0 : static_cast<image::Image*>(container.get())->ReportUseCounters();
1423 0 : }
1424 :
1425 : } // namespace dom
1426 : } // namespace mozilla
1427 :
|