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