Merge pull request #12635 from calixteman/js_display_evts
JS -- Send events to the sandbox from annotation layer
This commit is contained in:
		
						commit
						3c603fb28b
					
				| @ -365,7 +365,11 @@ class LinkAnnotationElement extends AnnotationElement { | |||||||
|       parameters.data.url || |       parameters.data.url || | ||||||
|       parameters.data.dest || |       parameters.data.dest || | ||||||
|       parameters.data.action || |       parameters.data.action || | ||||||
|       parameters.data.isTooltipOnly |       parameters.data.isTooltipOnly || | ||||||
|  |       (parameters.data.actions && | ||||||
|  |         (parameters.data.actions.Action || | ||||||
|  |           parameters.data.actions.MouseUp || | ||||||
|  |           parameters.data.actions.MouseDown)) | ||||||
|     ); |     ); | ||||||
|     super(parameters, { isRenderable, createQuadrilaterals: true }); |     super(parameters, { isRenderable, createQuadrilaterals: true }); | ||||||
|   } |   } | ||||||
| @ -387,6 +391,13 @@ class LinkAnnotationElement extends AnnotationElement { | |||||||
|       this._bindNamedAction(link, data.action); |       this._bindNamedAction(link, data.action); | ||||||
|     } else if (data.dest) { |     } else if (data.dest) { | ||||||
|       this._bindLink(link, data.dest); |       this._bindLink(link, data.dest); | ||||||
|  |     } else if ( | ||||||
|  |       data.actions && | ||||||
|  |       (data.actions.Action || data.actions.MouseUp || data.actions.MouseDown) && | ||||||
|  |       this.enableScripting && | ||||||
|  |       this.hasJSActions | ||||||
|  |     ) { | ||||||
|  |       this._bindJSAction(link); | ||||||
|     } else { |     } else { | ||||||
|       this._bindLink(link, ""); |       this._bindLink(link, ""); | ||||||
|     } |     } | ||||||
| @ -443,6 +454,42 @@ class LinkAnnotationElement extends AnnotationElement { | |||||||
|     }; |     }; | ||||||
|     link.className = "internalLink"; |     link.className = "internalLink"; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Bind JS actions to the link element. | ||||||
|  |    * | ||||||
|  |    * @private | ||||||
|  |    * @param {Object} link | ||||||
|  |    * @param {Object} data | ||||||
|  |    * @memberof LinkAnnotationElement | ||||||
|  |    */ | ||||||
|  |   _bindJSAction(link) { | ||||||
|  |     link.href = this.linkService.getAnchorUrl("#"); | ||||||
|  |     const { data } = this; | ||||||
|  |     const map = new Map([ | ||||||
|  |       ["Action", "onclick"], | ||||||
|  |       ["MouseUp", "onmouseup"], | ||||||
|  |       ["MouseDown", "onmousedown"], | ||||||
|  |     ]); | ||||||
|  |     for (const name of Object.keys(data.actions)) { | ||||||
|  |       const jsName = map.get(name); | ||||||
|  |       if (!jsName) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       link[jsName] = () => { | ||||||
|  |         window.dispatchEvent( | ||||||
|  |           new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |             detail: { | ||||||
|  |               id: data.id, | ||||||
|  |               name, | ||||||
|  |             }, | ||||||
|  |           }) | ||||||
|  |         ); | ||||||
|  |         return false; | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     link.className = "internalLink"; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class TextAnnotationElement extends AnnotationElement { | class TextAnnotationElement extends AnnotationElement { | ||||||
| @ -488,6 +535,53 @@ class WidgetAnnotationElement extends AnnotationElement { | |||||||
| 
 | 
 | ||||||
|     return this.container; |     return this.container; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   _getKeyModifier(event) { | ||||||
|  |     return ( | ||||||
|  |       (navigator.platform.includes("Win") && event.ctrlKey) || | ||||||
|  |       (navigator.platform.includes("Mac") && event.metaKey) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _setEventListener(element, baseName, eventName, valueGetter) { | ||||||
|  |     if (this.data.actions && eventName.replace(" ", "") in this.data.actions) { | ||||||
|  |       if (baseName.includes("mouse")) { | ||||||
|  |         // Mouse events
 | ||||||
|  |         element.addEventListener(baseName, event => { | ||||||
|  |           window.dispatchEvent( | ||||||
|  |             new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |               detail: { | ||||||
|  |                 id: this.data.id, | ||||||
|  |                 name: eventName, | ||||||
|  |                 value: valueGetter(event), | ||||||
|  |                 shift: event.shiftKey, | ||||||
|  |                 modifier: this._getKeyModifier(event), | ||||||
|  |               }, | ||||||
|  |             }) | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         // Non mouse event
 | ||||||
|  |         element.addEventListener(baseName, event => { | ||||||
|  |           window.dispatchEvent( | ||||||
|  |             new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |               detail: { | ||||||
|  |                 id: this.data.id, | ||||||
|  |                 name: eventName, | ||||||
|  |                 value: event.target.checked, | ||||||
|  |               }, | ||||||
|  |             }) | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _setEventListeners(element, names, getter) { | ||||||
|  |     for (const [baseName, eventName] of names) { | ||||||
|  |       this._setEventListener(element, baseName, eventName, getter); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class TextWidgetAnnotationElement extends WidgetAnnotationElement { | class TextWidgetAnnotationElement extends WidgetAnnotationElement { | ||||||
| @ -496,6 +590,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|       parameters.renderInteractiveForms || |       parameters.renderInteractiveForms || | ||||||
|       (!parameters.data.hasAppearance && !!parameters.data.fieldValue); |       (!parameters.data.hasAppearance && !!parameters.data.fieldValue); | ||||||
|     super(parameters, { isRenderable }); |     super(parameters, { isRenderable }); | ||||||
|  |     this.mouseState = parameters.mouseState; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
| @ -513,6 +608,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|       const textContent = storage.getOrCreateValue(id, { |       const textContent = storage.getOrCreateValue(id, { | ||||||
|         value: this.data.fieldValue, |         value: this.data.fieldValue, | ||||||
|       }).value; |       }).value; | ||||||
|  |       const elementData = { | ||||||
|  |         userValue: null, | ||||||
|  |         formattedValue: null, | ||||||
|  |         beforeInputSelectionRange: null, | ||||||
|  |         beforeInputValue: null, | ||||||
|  |       }; | ||||||
| 
 | 
 | ||||||
|       if (this.data.multiLine) { |       if (this.data.multiLine) { | ||||||
|         element = document.createElement("textarea"); |         element = document.createElement("textarea"); | ||||||
| @ -523,104 +624,196 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|         element.setAttribute("value", textContent); |         element.setAttribute("value", textContent); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       element.userValue = textContent; |       elementData.userValue = textContent; | ||||||
|       element.setAttribute("id", id); |       element.setAttribute("id", id); | ||||||
| 
 | 
 | ||||||
|       element.addEventListener("input", function (event) { |       element.addEventListener("input", function (event) { | ||||||
|         storage.setValue(id, { value: event.target.value }); |         storage.setValue(id, { value: event.target.value }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       element.addEventListener("blur", function (event) { |       let blurListener = event => { | ||||||
|  |         if (elementData.formattedValue) { | ||||||
|  |           event.target.value = elementData.formattedValue; | ||||||
|  |         } | ||||||
|         event.target.setSelectionRange(0, 0); |         event.target.setSelectionRange(0, 0); | ||||||
|       }); |         elementData.beforeInputSelectionRange = null; | ||||||
|  |       }; | ||||||
| 
 | 
 | ||||||
|       if (this.enableScripting && this.hasJSActions) { |       if (this.enableScripting && this.hasJSActions) { | ||||||
|         element.addEventListener("focus", event => { |         element.addEventListener("focus", event => { | ||||||
|           if (event.target.userValue) { |           if (elementData.userValue) { | ||||||
|             event.target.value = event.target.userValue; |             event.target.value = elementData.userValue; | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         if (this.data.actions) { |         element.addEventListener("updateFromSandbox", function (event) { | ||||||
|           element.addEventListener("updateFromSandbox", function (event) { |           const { detail } = event; | ||||||
|             const detail = event.detail; |           const actions = { | ||||||
|             const actions = { |             value() { | ||||||
|               value() { |               elementData.userValue = detail.value || ""; | ||||||
|                 const value = detail.value; |               storage.setValue(id, { value: elementData.userValue.toString() }); | ||||||
|                 if (value === undefined || value === null) { |             }, | ||||||
|                   // remove data
 |             valueAsString() { | ||||||
|                   event.target.userValue = ""; |               elementData.formattedValue = detail.valueAsString || ""; | ||||||
|                 } else { |               if (event.target !== document.activeElement) { | ||||||
|                   event.target.userValue = value; |                 // Input hasn't the focus so display formatted string
 | ||||||
|                 } |                 event.target.value = elementData.formattedValue; | ||||||
|               }, |  | ||||||
|               valueAsString() { |  | ||||||
|                 const value = detail.valueAsString; |  | ||||||
|                 if (value === undefined || value === null) { |  | ||||||
|                   // remove data
 |  | ||||||
|                   event.target.value = ""; |  | ||||||
|                 } else { |  | ||||||
|                   event.target.value = value; |  | ||||||
|                 } |  | ||||||
|                 storage.setValue(id, event.target.value); |  | ||||||
|               }, |  | ||||||
|               focus() { |  | ||||||
|                 event.target.focus({ preventScroll: false }); |  | ||||||
|               }, |  | ||||||
|               userName() { |  | ||||||
|                 const tooltip = detail.userName; |  | ||||||
|                 event.target.title = tooltip; |  | ||||||
|               }, |  | ||||||
|               hidden() { |  | ||||||
|                 event.target.style.display = detail.hidden ? "none" : "block"; |  | ||||||
|               }, |  | ||||||
|               editable() { |  | ||||||
|                 event.target.disabled = !detail.editable; |  | ||||||
|               }, |  | ||||||
|               selRange() { |  | ||||||
|                 const [selStart, selEnd] = detail.selRange; |  | ||||||
|                 if (selStart >= 0 && selEnd < event.target.value.length) { |  | ||||||
|                   event.target.setSelectionRange(selStart, selEnd); |  | ||||||
|                 } |  | ||||||
|               }, |  | ||||||
|               strokeColor() { |  | ||||||
|                 const color = detail.strokeColor; |  | ||||||
|                 event.target.style.color = ColorConverters[`${color[0]}_HTML`]( |  | ||||||
|                   color.slice(1) |  | ||||||
|                 ); |  | ||||||
|               }, |  | ||||||
|             }; |  | ||||||
|             for (const name of Object.keys(detail)) { |  | ||||||
|               if (name in actions) { |  | ||||||
|                 actions[name](); |  | ||||||
|               } |               } | ||||||
|  |               storage.setValue(id, { | ||||||
|  |                 formattedValue: elementData.formattedValue, | ||||||
|  |               }); | ||||||
|  |             }, | ||||||
|  |             focus() { | ||||||
|  |               setTimeout(() => event.target.focus({ preventScroll: false }), 0); | ||||||
|  |             }, | ||||||
|  |             userName() { | ||||||
|  |               // tooltip
 | ||||||
|  |               event.target.title = detail.userName; | ||||||
|  |             }, | ||||||
|  |             hidden() { | ||||||
|  |               event.target.style.visibility = detail.hidden | ||||||
|  |                 ? "hidden" | ||||||
|  |                 : "visible"; | ||||||
|  |               storage.setValue(id, { hidden: detail.hidden }); | ||||||
|  |             }, | ||||||
|  |             editable() { | ||||||
|  |               event.target.disabled = !detail.editable; | ||||||
|  |             }, | ||||||
|  |             selRange() { | ||||||
|  |               const [selStart, selEnd] = detail.selRange; | ||||||
|  |               if (selStart >= 0 && selEnd < event.target.value.length) { | ||||||
|  |                 event.target.setSelectionRange(selStart, selEnd); | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             strokeColor() { | ||||||
|  |               const color = detail.strokeColor; | ||||||
|  |               event.target.style.color = ColorConverters[`${color[0]}_HTML`]( | ||||||
|  |                 color.slice(1) | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           }; | ||||||
|  |           Object.keys(detail) | ||||||
|  |             .filter(name => name in actions) | ||||||
|  |             .forEach(name => actions[name]()); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (this.data.actions) { | ||||||
|  |           // Even if the field hasn't any actions
 | ||||||
|  |           // leaving it can still trigger some actions with Calculate
 | ||||||
|  |           element.addEventListener("keydown", event => { | ||||||
|  |             elementData.beforeInputValue = event.target.value; | ||||||
|  |             // if the key is one of Escape, Enter or Tab
 | ||||||
|  |             // then the data are committed
 | ||||||
|  |             let commitKey = -1; | ||||||
|  |             if (event.key === "Escape") { | ||||||
|  |               commitKey = 0; | ||||||
|  |             } else if (event.key === "Enter") { | ||||||
|  |               commitKey = 2; | ||||||
|  |             } else if (event.key === "Tab") { | ||||||
|  |               commitKey = 3; | ||||||
|  |             } | ||||||
|  |             if (commitKey === -1) { | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |             // Save the entered value
 | ||||||
|  |             elementData.userValue = event.target.value; | ||||||
|  |             window.dispatchEvent( | ||||||
|  |               new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |                 detail: { | ||||||
|  |                   id, | ||||||
|  |                   name: "Keystroke", | ||||||
|  |                   value: event.target.value, | ||||||
|  |                   willCommit: true, | ||||||
|  |                   commitKey, | ||||||
|  |                   selStart: event.target.selectionStart, | ||||||
|  |                   selEnd: event.target.selectionEnd, | ||||||
|  |                 }, | ||||||
|  |               }) | ||||||
|  |             ); | ||||||
|  |           }); | ||||||
|  |           const _blurListener = blurListener; | ||||||
|  |           blurListener = null; | ||||||
|  |           element.addEventListener("blur", event => { | ||||||
|  |             if (this.mouseState.isDown) { | ||||||
|  |               // Focus out using the mouse: data are committed
 | ||||||
|  |               elementData.userValue = event.target.value; | ||||||
|  |               window.dispatchEvent( | ||||||
|  |                 new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |                   detail: { | ||||||
|  |                     id, | ||||||
|  |                     name: "Keystroke", | ||||||
|  |                     value: event.target.value, | ||||||
|  |                     willCommit: true, | ||||||
|  |                     commitKey: 1, | ||||||
|  |                     selStart: event.target.selectionStart, | ||||||
|  |                     selEnd: event.target.selectionEnd, | ||||||
|  |                   }, | ||||||
|  |                 }) | ||||||
|  |               ); | ||||||
|  |             } | ||||||
|  |             _blurListener(event); | ||||||
|  |           }); | ||||||
|  |           element.addEventListener("mousedown", event => { | ||||||
|  |             elementData.beforeInputValue = event.target.value; | ||||||
|  |             elementData.beforeInputSelectionRange = null; | ||||||
|  |           }); | ||||||
|  |           element.addEventListener("keyup", event => { | ||||||
|  |             // keyup is triggered after input
 | ||||||
|  |             if (event.target.selectionStart === event.target.selectionEnd) { | ||||||
|  |               elementData.beforeInputSelectionRange = null; | ||||||
|             } |             } | ||||||
|           }); |           }); | ||||||
|  |           element.addEventListener("select", event => { | ||||||
|  |             elementData.beforeInputSelectionRange = [ | ||||||
|  |               event.target.selectionStart, | ||||||
|  |               event.target.selectionEnd, | ||||||
|  |             ]; | ||||||
|  |           }); | ||||||
| 
 | 
 | ||||||
|           for (const eventType of Object.keys(this.data.actions)) { |           if ("Keystroke" in this.data.actions) { | ||||||
|             switch (eventType) { |             // We should use beforeinput but this
 | ||||||
|               case "Format": |             // event isn't available in Firefox
 | ||||||
|                 element.addEventListener("change", function (event) { |             element.addEventListener("input", event => { | ||||||
|                   window.dispatchEvent( |               let selStart = -1; | ||||||
|                     new CustomEvent("dispatchEventInSandbox", { |               let selEnd = -1; | ||||||
|                       detail: { |               if (elementData.beforeInputSelectionRange) { | ||||||
|                         id, |                 [selStart, selEnd] = elementData.beforeInputSelectionRange; | ||||||
|                         name: "Keystroke", |               } | ||||||
|                         value: event.target.value, |               window.dispatchEvent( | ||||||
|                         willCommit: true, |                 new CustomEvent("dispatchEventInSandbox", { | ||||||
|                         commitKey: 1, |                   detail: { | ||||||
|                         selStart: event.target.selectionStart, |                     id, | ||||||
|                         selEnd: event.target.selectionEnd, |                     name: "Keystroke", | ||||||
|                       }, |                     value: elementData.beforeInputValue, | ||||||
|                     }) |                     change: event.data, | ||||||
|                   ); |                     willCommit: false, | ||||||
|                 }); |                     selStart, | ||||||
|                 break; |                     selEnd, | ||||||
|             } |                   }, | ||||||
|  |                 }) | ||||||
|  |               ); | ||||||
|  |             }); | ||||||
|           } |           } | ||||||
|  | 
 | ||||||
|  |           this._setEventListeners( | ||||||
|  |             element, | ||||||
|  |             [ | ||||||
|  |               ["focus", "Focus"], | ||||||
|  |               ["blur", "Blur"], | ||||||
|  |               ["mousedown", "Mouse Down"], | ||||||
|  |               ["mouseenter", "Mouse Enter"], | ||||||
|  |               ["mouseleave", "Mouse Exit"], | ||||||
|  |               ["mouseup", "MouseUp"], | ||||||
|  |             ], | ||||||
|  |             event => event.target.value | ||||||
|  |           ); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if (blurListener) { | ||||||
|  |         element.addEventListener("blur", blurListener); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       element.disabled = this.data.readOnly; |       element.disabled = this.data.readOnly; | ||||||
|       element.name = this.data.fieldName; |       element.name = this.data.fieldName; | ||||||
| 
 | 
 | ||||||
| @ -715,6 +908,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|     if (value) { |     if (value) { | ||||||
|       element.setAttribute("checked", true); |       element.setAttribute("checked", true); | ||||||
|     } |     } | ||||||
|  |     element.setAttribute("id", id); | ||||||
| 
 | 
 | ||||||
|     element.addEventListener("change", function (event) { |     element.addEventListener("change", function (event) { | ||||||
|       const name = event.target.name; |       const name = event.target.name; | ||||||
| @ -730,6 +924,48 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|       storage.setValue(id, { value: event.target.checked }); |       storage.setValue(id, { value: event.target.checked }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     if (this.enableScripting && this.hasJSActions) { | ||||||
|  |       element.addEventListener("updateFromSandbox", event => { | ||||||
|  |         const { detail } = event; | ||||||
|  |         const actions = { | ||||||
|  |           value() { | ||||||
|  |             event.target.checked = detail.value !== "Off"; | ||||||
|  |             storage.setValue(id, { value: event.target.checked }); | ||||||
|  |           }, | ||||||
|  |           focus() { | ||||||
|  |             setTimeout(() => event.target.focus({ preventScroll: false }), 0); | ||||||
|  |           }, | ||||||
|  |           hidden() { | ||||||
|  |             event.target.style.visibility = detail.hidden | ||||||
|  |               ? "hidden" | ||||||
|  |               : "visible"; | ||||||
|  |             storage.setValue(id, { hidden: detail.hidden }); | ||||||
|  |           }, | ||||||
|  |           editable() { | ||||||
|  |             event.target.disabled = !detail.editable; | ||||||
|  |           }, | ||||||
|  |         }; | ||||||
|  |         Object.keys(detail) | ||||||
|  |           .filter(name => name in actions) | ||||||
|  |           .forEach(name => actions[name]()); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       this._setEventListeners( | ||||||
|  |         element, | ||||||
|  |         [ | ||||||
|  |           ["change", "Validate"], | ||||||
|  |           ["change", "Action"], | ||||||
|  |           ["focus", "Focus"], | ||||||
|  |           ["blur", "Blur"], | ||||||
|  |           ["mousedown", "Mouse Down"], | ||||||
|  |           ["mouseenter", "Mouse Enter"], | ||||||
|  |           ["mouseleave", "Mouse Exit"], | ||||||
|  |           ["mouseup", "MouseUp"], | ||||||
|  |         ], | ||||||
|  |         event => event.target.checked | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     this.container.appendChild(element); |     this.container.appendChild(element); | ||||||
|     return this.container; |     return this.container; | ||||||
|   } |   } | ||||||
| @ -756,20 +992,69 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|     if (value) { |     if (value) { | ||||||
|       element.setAttribute("checked", true); |       element.setAttribute("checked", true); | ||||||
|     } |     } | ||||||
|  |     element.setAttribute("pdfButtonValue", data.buttonValue); | ||||||
|  |     element.setAttribute("id", id); | ||||||
| 
 | 
 | ||||||
|     element.addEventListener("change", function (event) { |     element.addEventListener("change", function (event) { | ||||||
|       const name = event.target.name; |       const target = event.target; | ||||||
|       for (const radio of document.getElementsByName(name)) { |       for (const radio of document.getElementsByName(event.target.name)) { | ||||||
|         if (radio !== event.target) { |         if (radio !== target) { | ||||||
|           storage.setValue( |           storage.setValue(radio.getAttribute("id"), { value: false }); | ||||||
|             radio.parentNode.getAttribute("data-annotation-id"), |  | ||||||
|             { value: false } |  | ||||||
|           ); |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       storage.setValue(id, { value: event.target.checked }); |       storage.setValue(id, { value: target.checked }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     if (this.enableScripting && this.hasJSActions) { | ||||||
|  |       element.addEventListener("updateFromSandbox", event => { | ||||||
|  |         const { detail } = event; | ||||||
|  |         const actions = { | ||||||
|  |           value() { | ||||||
|  |             const fieldValue = detail.value; | ||||||
|  |             for (const radio of document.getElementsByName(event.target.name)) { | ||||||
|  |               const radioId = radio.getAttribute("id"); | ||||||
|  |               if (fieldValue === radio.getAttribute("pdfButtonValue")) { | ||||||
|  |                 radio.setAttribute("checked", true); | ||||||
|  |                 storage.setValue(radioId, { value: true }); | ||||||
|  |               } else { | ||||||
|  |                 storage.setValue(radioId, { value: false }); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           focus() { | ||||||
|  |             setTimeout(() => event.target.focus({ preventScroll: false }), 0); | ||||||
|  |           }, | ||||||
|  |           hidden() { | ||||||
|  |             event.target.style.visibility = detail.hidden | ||||||
|  |               ? "hidden" | ||||||
|  |               : "visible"; | ||||||
|  |             storage.setValue(id, { hidden: detail.hidden }); | ||||||
|  |           }, | ||||||
|  |           editable() { | ||||||
|  |             event.target.disabled = !detail.editable; | ||||||
|  |           }, | ||||||
|  |         }; | ||||||
|  |         Object.keys(detail) | ||||||
|  |           .filter(name => name in actions) | ||||||
|  |           .forEach(name => actions[name]()); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       this._setEventListeners( | ||||||
|  |         element, | ||||||
|  |         [ | ||||||
|  |           ["change", "Validate"], | ||||||
|  |           ["change", "Action"], | ||||||
|  |           ["focus", "Focus"], | ||||||
|  |           ["blur", "Blur"], | ||||||
|  |           ["mousedown", "Mouse Down"], | ||||||
|  |           ["mouseenter", "Mouse Enter"], | ||||||
|  |           ["mouseleave", "Mouse Exit"], | ||||||
|  |           ["mouseup", "MouseUp"], | ||||||
|  |         ], | ||||||
|  |         event => event.target.checked | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     this.container.appendChild(element); |     this.container.appendChild(element); | ||||||
|     return this.container; |     return this.container; | ||||||
|   } |   } | ||||||
| @ -816,6 +1101,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|     const selectElement = document.createElement("select"); |     const selectElement = document.createElement("select"); | ||||||
|     selectElement.disabled = this.data.readOnly; |     selectElement.disabled = this.data.readOnly; | ||||||
|     selectElement.name = this.data.fieldName; |     selectElement.name = this.data.fieldName; | ||||||
|  |     selectElement.setAttribute("id", id); | ||||||
| 
 | 
 | ||||||
|     if (!this.data.combo) { |     if (!this.data.combo) { | ||||||
|       // List boxes have a size and (optionally) multiple selection.
 |       // List boxes have a size and (optionally) multiple selection.
 | ||||||
| @ -836,11 +1122,77 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { | |||||||
|       selectElement.appendChild(optionElement); |       selectElement.appendChild(optionElement); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     selectElement.addEventListener("input", function (event) { |     function getValue(event) { | ||||||
|       const options = event.target.options; |       const options = event.target.options; | ||||||
|       const value = options[options.selectedIndex].value; |       return options[options.selectedIndex].value; | ||||||
|       storage.setValue(id, { value }); |     } | ||||||
|     }); | 
 | ||||||
|  |     if (this.enableScripting && this.hasJSActions) { | ||||||
|  |       selectElement.addEventListener("updateFromSandbox", event => { | ||||||
|  |         const { detail } = event; | ||||||
|  |         const actions = { | ||||||
|  |           value() { | ||||||
|  |             const options = event.target.options; | ||||||
|  |             const value = detail.value; | ||||||
|  |             const i = options.indexOf(value); | ||||||
|  |             if (i !== -1) { | ||||||
|  |               options.selectedIndex = i; | ||||||
|  |               storage.setValue(id, { value }); | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           focus() { | ||||||
|  |             setTimeout(() => event.target.focus({ preventScroll: false }), 0); | ||||||
|  |           }, | ||||||
|  |           hidden() { | ||||||
|  |             event.target.style.visibility = detail.hidden | ||||||
|  |               ? "hidden" | ||||||
|  |               : "visible"; | ||||||
|  |             storage.setValue(id, { hidden: detail.hidden }); | ||||||
|  |           }, | ||||||
|  |           editable() { | ||||||
|  |             event.target.disabled = !detail.editable; | ||||||
|  |           }, | ||||||
|  |         }; | ||||||
|  |         Object.keys(detail) | ||||||
|  |           .filter(name => name in actions) | ||||||
|  |           .forEach(name => actions[name]()); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       selectElement.addEventListener("input", function (event) { | ||||||
|  |         const value = getValue(event); | ||||||
|  |         storage.setValue(id, { value }); | ||||||
|  | 
 | ||||||
|  |         window.dispatchEvent( | ||||||
|  |           new CustomEvent("dispatchEventInSandbox", { | ||||||
|  |             detail: { | ||||||
|  |               id, | ||||||
|  |               name: "Keystroke", | ||||||
|  |               changeEx: value, | ||||||
|  |               willCommit: true, | ||||||
|  |               commitKey: 1, | ||||||
|  |               keyDown: false, | ||||||
|  |             }, | ||||||
|  |           }) | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       this._setEventListeners( | ||||||
|  |         selectElement, | ||||||
|  |         [ | ||||||
|  |           ["focus", "Focus"], | ||||||
|  |           ["blur", "Blur"], | ||||||
|  |           ["mousedown", "Mouse Down"], | ||||||
|  |           ["mouseenter", "Mouse Enter"], | ||||||
|  |           ["mouseleave", "Mouse Exit"], | ||||||
|  |           ["mouseup", "MouseUp"], | ||||||
|  |         ], | ||||||
|  |         event => event.target.checked | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       selectElement.addEventListener("input", function (event) { | ||||||
|  |         storage.setValue(id, { value: getValue(event) }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     this.container.appendChild(selectElement); |     this.container.appendChild(selectElement); | ||||||
|     return this.container; |     return this.container; | ||||||
| @ -1599,6 +1951,7 @@ class AnnotationLayer { | |||||||
|           parameters.annotationStorage || new AnnotationStorage(), |           parameters.annotationStorage || new AnnotationStorage(), | ||||||
|         enableScripting: parameters.enableScripting, |         enableScripting: parameters.enableScripting, | ||||||
|         hasJSActions: parameters.hasJSActions, |         hasJSActions: parameters.hasJSActions, | ||||||
|  |         mouseState: parameters.mouseState, | ||||||
|       }); |       }); | ||||||
|       if (element.isRenderable) { |       if (element.isRenderable) { | ||||||
|         const rendered = element.render(); |         const rendered = element.render(); | ||||||
|  | |||||||
| @ -913,6 +913,7 @@ class Doc extends PDFObject { | |||||||
|         const field = this.getField(fieldName); |         const field = this.getField(fieldName); | ||||||
|         if (field) { |         if (field) { | ||||||
|           field.value = field.defaultValue; |           field.value = field.defaultValue; | ||||||
|  |           field.valueAsString = field.value; | ||||||
|           mustCalculate = true; |           mustCalculate = true; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @ -920,6 +921,7 @@ class Doc extends PDFObject { | |||||||
|       mustCalculate = this._fields.size !== 0; |       mustCalculate = this._fields.size !== 0; | ||||||
|       for (const field of this._fields.values()) { |       for (const field of this._fields.values()) { | ||||||
|         field.value = field.defaultValue; |         field.value = field.defaultValue; | ||||||
|  |         field.valueAsString = field.value; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (mustCalculate) { |     if (mustCalculate) { | ||||||
|  | |||||||
| @ -66,7 +66,9 @@ class Field extends PDFObject { | |||||||
|     this.type = data.type; |     this.type = data.type; | ||||||
|     this.userName = data.userName; |     this.userName = data.userName; | ||||||
|     this.value = data.value || ""; |     this.value = data.value || ""; | ||||||
|     this.valueAsString = data.valueAsString; | 
 | ||||||
|  |     // Need getter/setter
 | ||||||
|  |     this._valueAsString = data.valueAsString; | ||||||
| 
 | 
 | ||||||
|     // Private
 |     // Private
 | ||||||
|     this._document = data.doc; |     this._document = data.doc; | ||||||
| @ -107,6 +109,28 @@ class Field extends PDFObject { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get valueAsString() { | ||||||
|  |     return this._valueAsString; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set valueAsString(val) { | ||||||
|  |     this._valueAsString = val ? val.toString() : ""; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _getFunction(code, actionName) { | ||||||
|  |     try { | ||||||
|  |       // This eval is running in a sandbox so it's safe to use Function
 | ||||||
|  |       // eslint-disable-next-line no-new-func
 | ||||||
|  |       return Function("event", `with (this) {${code}}`).bind(this._document); | ||||||
|  |     } catch (error) { | ||||||
|  |       const value = | ||||||
|  |         `"${error.toString()}" for action ` + | ||||||
|  |         `"${actionName}" in object ${this._id}.`; | ||||||
|  |       this._send({ command: "error", value }); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setAction(cTrigger, cScript) { |   setAction(cTrigger, cScript) { | ||||||
|     if (typeof cTrigger !== "string" || typeof cScript !== "string") { |     if (typeof cTrigger !== "string" || typeof cScript !== "string") { | ||||||
|       return; |       return; | ||||||
| @ -114,10 +138,10 @@ class Field extends PDFObject { | |||||||
|     if (!(cTrigger in this._actions)) { |     if (!(cTrigger in this._actions)) { | ||||||
|       this._actions[cTrigger] = []; |       this._actions[cTrigger] = []; | ||||||
|     } |     } | ||||||
|     this._actions[cTrigger].push( |     const fun = this._getFunction(cScript, cTrigger); | ||||||
|       // eslint-disable-next-line no-new-func
 |     if (fun) { | ||||||
|       Function("event", `with (this) {${cScript}}`).bind(this._document) |       this._actions[cTrigger].push(fun); | ||||||
|     ); |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setFocus() { |   setFocus() { | ||||||
| @ -127,16 +151,13 @@ class Field extends PDFObject { | |||||||
|   _createActionsMap(actions) { |   _createActionsMap(actions) { | ||||||
|     const actionsMap = new Map(); |     const actionsMap = new Map(); | ||||||
|     if (actions) { |     if (actions) { | ||||||
|       const doc = this._document; |  | ||||||
|       for (const [eventType, actionsForEvent] of Object.entries(actions)) { |       for (const [eventType, actionsForEvent] of Object.entries(actions)) { | ||||||
|         // This stuff is running in a sandbox so it's safe to use Function
 |         const functions = actionsForEvent | ||||||
|         actionsMap.set( |           .map(action => this._getFunction(action, eventType)) | ||||||
|           eventType, |           .filter(fun => !!fun); | ||||||
|           actionsForEvent.map(action => |         if (functions.length > 0) { | ||||||
|             // eslint-disable-next-line no-new-func
 |           actionsMap.set(eventType, functions); | ||||||
|             Function("event", `with (this) {${action}}`).bind(doc) |         } | ||||||
|           ) |  | ||||||
|         ); |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return actionsMap; |     return actionsMap; | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ class ProxyHandler { | |||||||
|       obj[prop] = value; |       obj[prop] = value; | ||||||
|       if (obj._send && obj._id !== null && typeof old !== "function") { |       if (obj._send && obj._id !== null && typeof old !== "function") { | ||||||
|         const data = { id: obj._id }; |         const data = { id: obj._id }; | ||||||
|         data[prop] = value; |         data[prop] = obj[prop]; | ||||||
| 
 | 
 | ||||||
|         // send the updated value to the other side
 |         // send the updated value to the other side
 | ||||||
|         obj._send(data); |         obj._send(data); | ||||||
|  | |||||||
| @ -27,6 +27,44 @@ describe("Interaction", () => { | |||||||
|       await closePages(pages); |       await closePages(pages); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it("must show a text field and then make in invisible when content is removed", async () => { | ||||||
|  |       await Promise.all( | ||||||
|  |         pages.map(async ([browserName, page]) => { | ||||||
|  |           let visibility = await page.$eval( | ||||||
|  |             "#\\34 27R", | ||||||
|  |             el => getComputedStyle(el).visibility | ||||||
|  |           ); | ||||||
|  |           expect(visibility).withContext(`In ${browserName}`).toEqual("hidden"); | ||||||
|  | 
 | ||||||
|  |           await page.type("#\\34 16R", "3.14159", { delay: 200 }); | ||||||
|  |           await page.click("#\\34 19R"); | ||||||
|  | 
 | ||||||
|  |           visibility = await page.$eval( | ||||||
|  |             "#\\34 27R", | ||||||
|  |             el => getComputedStyle(el).visibility | ||||||
|  |           ); | ||||||
|  |           expect(visibility) | ||||||
|  |             .withContext(`In ${browserName}`) | ||||||
|  |             .toEqual("visible"); | ||||||
|  | 
 | ||||||
|  |           // Clear the textfield
 | ||||||
|  |           await page.click("#\\34 16R"); | ||||||
|  |           await page.keyboard.down("Control"); | ||||||
|  |           await page.keyboard.press("A"); | ||||||
|  |           await page.keyboard.up("Control"); | ||||||
|  |           await page.keyboard.press("Backspace"); | ||||||
|  |           // and leave it
 | ||||||
|  |           await page.click("#\\34 19R"); | ||||||
|  | 
 | ||||||
|  |           visibility = await page.$eval( | ||||||
|  |             "#\\34 27R", | ||||||
|  |             el => getComputedStyle(el).visibility | ||||||
|  |           ); | ||||||
|  |           expect(visibility).withContext(`In ${browserName}`).toEqual("hidden"); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it("must format the field with 2 digits and leave field with a click", async () => { |     it("must format the field with 2 digits and leave field with a click", async () => { | ||||||
|       await Promise.all( |       await Promise.all( | ||||||
|         pages.map(async ([browserName, page]) => { |         pages.map(async ([browserName, page]) => { | ||||||
| @ -34,6 +72,35 @@ describe("Interaction", () => { | |||||||
|           await page.click("#\\34 19R"); |           await page.click("#\\34 19R"); | ||||||
|           const text = await page.$eval("#\\34 16R", el => el.value); |           const text = await page.$eval("#\\34 16R", el => el.value); | ||||||
|           expect(text).withContext(`In ${browserName}`).toEqual("3,14"); |           expect(text).withContext(`In ${browserName}`).toEqual("3,14"); | ||||||
|  | 
 | ||||||
|  |           const sum = await page.$eval("#\\34 27R", el => el.value); | ||||||
|  |           expect(sum).withContext(`In ${browserName}`).toEqual("3,14"); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it("must format the field with 2 digits, leave field with a click and again", async () => { | ||||||
|  |       await Promise.all( | ||||||
|  |         pages.map(async ([browserName, page]) => { | ||||||
|  |           await page.type("#\\34 48R", "61803", { delay: 200 }); | ||||||
|  |           await page.click("#\\34 19R"); | ||||||
|  |           let text = await page.$eval("#\\34 48R", el => el.value); | ||||||
|  |           expect(text).withContext(`In ${browserName}`).toEqual("61.803,00"); | ||||||
|  | 
 | ||||||
|  |           await page.click("#\\34 48R"); | ||||||
|  |           text = await page.$eval("#\\34 48R", el => el.value); | ||||||
|  |           expect(text).withContext(`In ${browserName}`).toEqual("61803"); | ||||||
|  | 
 | ||||||
|  |           // Clear the textfield
 | ||||||
|  |           await page.keyboard.down("Control"); | ||||||
|  |           await page.keyboard.press("A"); | ||||||
|  |           await page.keyboard.up("Control"); | ||||||
|  |           await page.keyboard.press("Backspace"); | ||||||
|  | 
 | ||||||
|  |           await page.type("#\\34 48R", "1.61803", { delay: 200 }); | ||||||
|  |           await page.click("#\\34 19R"); | ||||||
|  |           text = await page.$eval("#\\34 48R", el => el.value); | ||||||
|  |           expect(text).withContext(`In ${browserName}`).toEqual("1,62"); | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|     }); |     }); | ||||||
| @ -45,6 +112,67 @@ describe("Interaction", () => { | |||||||
|           await page.keyboard.press("Tab"); |           await page.keyboard.press("Tab"); | ||||||
|           const text = await page.$eval("#\\34 22R", el => el.value); |           const text = await page.$eval("#\\34 22R", el => el.value); | ||||||
|           expect(text).withContext(`In ${browserName}`).toEqual("2,72"); |           expect(text).withContext(`In ${browserName}`).toEqual("2,72"); | ||||||
|  | 
 | ||||||
|  |           const sum = await page.$eval("#\\34 27R", el => el.value); | ||||||
|  |           expect(sum).withContext(`In ${browserName}`).toEqual("5,86"); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it("must format the field with 2 digits and hit ESC", async () => { | ||||||
|  |       await Promise.all( | ||||||
|  |         pages.map(async ([browserName, page]) => { | ||||||
|  |           let sum = await page.$eval("#\\34 71R", el => el.value); | ||||||
|  |           expect(sum).withContext(`In ${browserName}`).toEqual("4,24"); | ||||||
|  | 
 | ||||||
|  |           await page.type("#\\34 36R", "0.69314", { delay: 200 }); | ||||||
|  |           await page.keyboard.press("Escape"); | ||||||
|  |           const text = await page.$eval("#\\34 36R", el => el.value); | ||||||
|  |           expect(text).withContext(`In ${browserName}`).toEqual("0.69314"); | ||||||
|  | 
 | ||||||
|  |           sum = await page.$eval("#\\34 71R", el => el.value); | ||||||
|  |           expect(sum).withContext(`In ${browserName}`).toEqual("3,55"); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it("must format the field with 2 digits on key ENTER", async () => { | ||||||
|  |       await Promise.all( | ||||||
|  |         pages.map(async ([browserName, page]) => { | ||||||
|  |           await page.type("#\\34 19R", "0.577215", { delay: 200 }); | ||||||
|  |           await page.keyboard.press("Enter"); | ||||||
|  |           const text = await page.$eval("#\\34 19R", el => el.value); | ||||||
|  |           expect(text).toEqual("0.577215"); | ||||||
|  | 
 | ||||||
|  |           const sum = await page.$eval("#\\34 27R", el => el.value); | ||||||
|  |           expect(sum).toEqual("6,44"); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it("must reset all", async () => { | ||||||
|  |       await Promise.all( | ||||||
|  |         pages.map(async ([browserName, page]) => { | ||||||
|  |           // this field has no actions but it must be cleared on reset
 | ||||||
|  |           await page.type("#\\34 05R", "employee", { delay: 200 }); | ||||||
|  | 
 | ||||||
|  |           // click on reset button
 | ||||||
|  |           await page.click("[data-annotation-id='402R']"); | ||||||
|  | 
 | ||||||
|  |           let text = await page.$eval("#\\34 16R", el => el.value); | ||||||
|  |           expect(text).toEqual(""); | ||||||
|  | 
 | ||||||
|  |           text = await page.$eval("#\\34 22R", el => el.value); | ||||||
|  |           expect(text).toEqual(""); | ||||||
|  | 
 | ||||||
|  |           text = await page.$eval("#\\34 19R", el => el.value); | ||||||
|  |           expect(text).toEqual(""); | ||||||
|  | 
 | ||||||
|  |           text = await page.$eval("#\\34 05R", el => el.value); | ||||||
|  |           expect(text).toEqual(""); | ||||||
|  | 
 | ||||||
|  |           const sum = await page.$eval("#\\34 27R", el => el.value); | ||||||
|  |           expect(sum).toEqual(""); | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -84,7 +84,7 @@ describe("Scripting", function () { | |||||||
|         return s; |         return s; | ||||||
|       } |       } | ||||||
|       const number = 123; |       const number = 123; | ||||||
|       const expected = ((number - 1) * number) / 2; |       const expected = (((number - 1) * number) / 2).toString(); | ||||||
|       const refId = getId(); |       const refId = getId(); | ||||||
| 
 | 
 | ||||||
|       const data = { |       const data = { | ||||||
| @ -1094,7 +1094,7 @@ describe("Scripting", function () { | |||||||
|           expect(send_queue.get(refIds[3])).toEqual({ |           expect(send_queue.get(refIds[3])).toEqual({ | ||||||
|             id: refIds[3], |             id: refIds[3], | ||||||
|             value: 1, |             value: 1, | ||||||
|             valueAsString: 1, |             valueAsString: "1", | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           await sandbox.dispatchEventInSandbox({ |           await sandbox.dispatchEventInSandbox({ | ||||||
| @ -1107,7 +1107,7 @@ describe("Scripting", function () { | |||||||
|           expect(send_queue.get(refIds[3])).toEqual({ |           expect(send_queue.get(refIds[3])).toEqual({ | ||||||
|             id: refIds[3], |             id: refIds[3], | ||||||
|             value: 3, |             value: 3, | ||||||
|             valueAsString: 3, |             valueAsString: "3", | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           await sandbox.dispatchEventInSandbox({ |           await sandbox.dispatchEventInSandbox({ | ||||||
| @ -1120,7 +1120,7 @@ describe("Scripting", function () { | |||||||
|           expect(send_queue.get(refIds[3])).toEqual({ |           expect(send_queue.get(refIds[3])).toEqual({ | ||||||
|             id: refIds[3], |             id: refIds[3], | ||||||
|             value: 6, |             value: 6, | ||||||
|             valueAsString: 6, |             valueAsString: "6", | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           done(); |           done(); | ||||||
|  | |||||||
| @ -47,6 +47,7 @@ class AnnotationLayerBuilder { | |||||||
|     l10n = NullL10n, |     l10n = NullL10n, | ||||||
|     enableScripting = false, |     enableScripting = false, | ||||||
|     hasJSActionsPromise = null, |     hasJSActionsPromise = null, | ||||||
|  |     mouseState = null, | ||||||
|   }) { |   }) { | ||||||
|     this.pageDiv = pageDiv; |     this.pageDiv = pageDiv; | ||||||
|     this.pdfPage = pdfPage; |     this.pdfPage = pdfPage; | ||||||
| @ -58,6 +59,7 @@ class AnnotationLayerBuilder { | |||||||
|     this.annotationStorage = annotationStorage; |     this.annotationStorage = annotationStorage; | ||||||
|     this.enableScripting = enableScripting; |     this.enableScripting = enableScripting; | ||||||
|     this._hasJSActionsPromise = hasJSActionsPromise; |     this._hasJSActionsPromise = hasJSActionsPromise; | ||||||
|  |     this._mouseState = mouseState; | ||||||
| 
 | 
 | ||||||
|     this.div = null; |     this.div = null; | ||||||
|     this._cancelled = false; |     this._cancelled = false; | ||||||
| @ -93,6 +95,7 @@ class AnnotationLayerBuilder { | |||||||
|         annotationStorage: this.annotationStorage, |         annotationStorage: this.annotationStorage, | ||||||
|         enableScripting: this.enableScripting, |         enableScripting: this.enableScripting, | ||||||
|         hasJSActions, |         hasJSActions, | ||||||
|  |         mouseState: this._mouseState, | ||||||
|       }; |       }; | ||||||
| 
 | 
 | ||||||
|       if (this.div) { |       if (this.div) { | ||||||
| @ -139,6 +142,7 @@ class DefaultAnnotationLayerFactory { | |||||||
|    * @param {IL10n} l10n |    * @param {IL10n} l10n | ||||||
|    * @param {boolean} [enableScripting] |    * @param {boolean} [enableScripting] | ||||||
|    * @param {Promise<boolean>} [hasJSActionsPromise] |    * @param {Promise<boolean>} [hasJSActionsPromise] | ||||||
|  |    * @param {Object} [mouseState] | ||||||
|    * @returns {AnnotationLayerBuilder} |    * @returns {AnnotationLayerBuilder} | ||||||
|    */ |    */ | ||||||
|   createAnnotationLayerBuilder( |   createAnnotationLayerBuilder( | ||||||
| @ -149,7 +153,8 @@ class DefaultAnnotationLayerFactory { | |||||||
|     renderInteractiveForms = true, |     renderInteractiveForms = true, | ||||||
|     l10n = NullL10n, |     l10n = NullL10n, | ||||||
|     enableScripting = false, |     enableScripting = false, | ||||||
|     hasJSActionsPromise = null |     hasJSActionsPromise = null, | ||||||
|  |     mouseState = null | ||||||
|   ) { |   ) { | ||||||
|     return new AnnotationLayerBuilder({ |     return new AnnotationLayerBuilder({ | ||||||
|       pageDiv, |       pageDiv, | ||||||
| @ -161,6 +166,7 @@ class DefaultAnnotationLayerFactory { | |||||||
|       annotationStorage, |       annotationStorage, | ||||||
|       enableScripting, |       enableScripting, | ||||||
|       hasJSActionsPromise, |       hasJSActionsPromise, | ||||||
|  |       mouseState, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								web/app.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								web/app.js
									
									
									
									
									
								
							| @ -258,6 +258,7 @@ const PDFViewerApplication = { | |||||||
|   _wheelUnusedTicks: 0, |   _wheelUnusedTicks: 0, | ||||||
|   _idleCallbacks: new Set(), |   _idleCallbacks: new Set(), | ||||||
|   _scriptingInstance: null, |   _scriptingInstance: null, | ||||||
|  |   _mouseState: Object.create(null), | ||||||
| 
 | 
 | ||||||
|   // Called once when the document is loaded.
 |   // Called once when the document is loaded.
 | ||||||
|   async initialize(appConfig) { |   async initialize(appConfig) { | ||||||
| @ -501,6 +502,7 @@ const PDFViewerApplication = { | |||||||
|       useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), |       useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), | ||||||
|       maxCanvasPixels: AppOptions.get("maxCanvasPixels"), |       maxCanvasPixels: AppOptions.get("maxCanvasPixels"), | ||||||
|       enableScripting: AppOptions.get("enableScripting"), |       enableScripting: AppOptions.get("enableScripting"), | ||||||
|  |       mouseState: this._mouseState, | ||||||
|     }); |     }); | ||||||
|     pdfRenderingQueue.setViewer(this.pdfViewer); |     pdfRenderingQueue.setViewer(this.pdfViewer); | ||||||
|     pdfLinkService.setViewer(this.pdfViewer); |     pdfLinkService.setViewer(this.pdfViewer); | ||||||
| @ -1538,6 +1540,17 @@ const PDFViewerApplication = { | |||||||
|       dispatchEventInSandbox |       dispatchEventInSandbox | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     const mouseDown = event => { | ||||||
|  |       this._mouseState.isDown = true; | ||||||
|  |     }; | ||||||
|  |     const mouseUp = event => { | ||||||
|  |       this._mouseState.isDown = false; | ||||||
|  |     }; | ||||||
|  |     window.addEventListener("mousedown", mouseDown); | ||||||
|  |     this._scriptingInstance.events.set("mousedown", mouseDown); | ||||||
|  |     window.addEventListener("mouseup", mouseUp); | ||||||
|  |     this._scriptingInstance.events.set("mouseup", mouseUp); | ||||||
|  | 
 | ||||||
|     const dispatchEventName = generateRandomStringForSandbox(objects); |     const dispatchEventName = generateRandomStringForSandbox(objects); | ||||||
| 
 | 
 | ||||||
|     if (!this._contentLength) { |     if (!this._contentLength) { | ||||||
|  | |||||||
| @ -79,6 +79,7 @@ const DEFAULT_CACHE_SIZE = 10; | |||||||
|  * @property {IL10n} l10n - Localization service. |  * @property {IL10n} l10n - Localization service. | ||||||
|  * @property {boolean} [enableScripting] - Enable embedded script execution. |  * @property {boolean} [enableScripting] - Enable embedded script execution. | ||||||
|  *   The default value is `false`. |  *   The default value is `false`. | ||||||
|  |  * @property {Object} [mouseState] - The mouse button state. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| function PDFPageViewBuffer(size) { | function PDFPageViewBuffer(size) { | ||||||
| @ -194,6 +195,7 @@ class BaseViewer { | |||||||
|     this.maxCanvasPixels = options.maxCanvasPixels; |     this.maxCanvasPixels = options.maxCanvasPixels; | ||||||
|     this.l10n = options.l10n || NullL10n; |     this.l10n = options.l10n || NullL10n; | ||||||
|     this.enableScripting = options.enableScripting || false; |     this.enableScripting = options.enableScripting || false; | ||||||
|  |     this.mouseState = options.mouseState || null; | ||||||
| 
 | 
 | ||||||
|     this.defaultRenderingQueue = !options.renderingQueue; |     this.defaultRenderingQueue = !options.renderingQueue; | ||||||
|     if (this.defaultRenderingQueue) { |     if (this.defaultRenderingQueue) { | ||||||
| @ -533,6 +535,7 @@ class BaseViewer { | |||||||
|             maxCanvasPixels: this.maxCanvasPixels, |             maxCanvasPixels: this.maxCanvasPixels, | ||||||
|             l10n: this.l10n, |             l10n: this.l10n, | ||||||
|             enableScripting: this.enableScripting, |             enableScripting: this.enableScripting, | ||||||
|  |             mouseState: this.mouseState, | ||||||
|           }); |           }); | ||||||
|           this._pages.push(pageView); |           this._pages.push(pageView); | ||||||
|         } |         } | ||||||
| @ -1265,6 +1268,7 @@ class BaseViewer { | |||||||
|    * @param {IL10n} l10n |    * @param {IL10n} l10n | ||||||
|    * @param {boolean} [enableScripting] |    * @param {boolean} [enableScripting] | ||||||
|    * @param {Promise<boolean>} [hasJSActionsPromise] |    * @param {Promise<boolean>} [hasJSActionsPromise] | ||||||
|  |    * @param {Object} [mouseState] | ||||||
|    * @returns {AnnotationLayerBuilder} |    * @returns {AnnotationLayerBuilder} | ||||||
|    */ |    */ | ||||||
|   createAnnotationLayerBuilder( |   createAnnotationLayerBuilder( | ||||||
| @ -1275,7 +1279,8 @@ class BaseViewer { | |||||||
|     renderInteractiveForms = false, |     renderInteractiveForms = false, | ||||||
|     l10n = NullL10n, |     l10n = NullL10n, | ||||||
|     enableScripting = false, |     enableScripting = false, | ||||||
|     hasJSActionsPromise = null |     hasJSActionsPromise = null, | ||||||
|  |     mouseState = null | ||||||
|   ) { |   ) { | ||||||
|     return new AnnotationLayerBuilder({ |     return new AnnotationLayerBuilder({ | ||||||
|       pageDiv, |       pageDiv, | ||||||
| @ -1290,6 +1295,7 @@ class BaseViewer { | |||||||
|       enableScripting, |       enableScripting, | ||||||
|       hasJSActionsPromise: |       hasJSActionsPromise: | ||||||
|         hasJSActionsPromise || this.pdfDocument?.hasJSActions(), |         hasJSActionsPromise || this.pdfDocument?.hasJSActions(), | ||||||
|  |       mouseState, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -188,6 +188,7 @@ class IPDFAnnotationLayerFactory { | |||||||
|    * @param {IL10n} l10n |    * @param {IL10n} l10n | ||||||
|    * @param {boolean} [enableScripting] |    * @param {boolean} [enableScripting] | ||||||
|    * @param {Promise<boolean>} [hasJSActionsPromise] |    * @param {Promise<boolean>} [hasJSActionsPromise] | ||||||
|  |    * @param {Object} [mouseState] | ||||||
|    * @returns {AnnotationLayerBuilder} |    * @returns {AnnotationLayerBuilder} | ||||||
|    */ |    */ | ||||||
|   createAnnotationLayerBuilder( |   createAnnotationLayerBuilder( | ||||||
| @ -198,7 +199,8 @@ class IPDFAnnotationLayerFactory { | |||||||
|     renderInteractiveForms = true, |     renderInteractiveForms = true, | ||||||
|     l10n = undefined, |     l10n = undefined, | ||||||
|     enableScripting = false, |     enableScripting = false, | ||||||
|     hasJSActionsPromise = null |     hasJSActionsPromise = null, | ||||||
|  |     mouseState = null | ||||||
|   ) {} |   ) {} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -63,6 +63,7 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js"; | |||||||
|  * @property {IL10n} l10n - Localization service. |  * @property {IL10n} l10n - Localization service. | ||||||
|  * @property {boolean} [enableScripting] - Enable embedded script execution. |  * @property {boolean} [enableScripting] - Enable embedded script execution. | ||||||
|  *   The default value is `false`. |  *   The default value is `false`. | ||||||
|  |  * @property {Object} [mouseState] - The mouse button state. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216; | const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216; | ||||||
| @ -109,6 +110,7 @@ class PDFPageView { | |||||||
|     this.enableWebGL = options.enableWebGL || false; |     this.enableWebGL = options.enableWebGL || false; | ||||||
|     this.l10n = options.l10n || NullL10n; |     this.l10n = options.l10n || NullL10n; | ||||||
|     this.enableScripting = options.enableScripting || false; |     this.enableScripting = options.enableScripting || false; | ||||||
|  |     this.mouseState = options.mouseState || null; | ||||||
| 
 | 
 | ||||||
|     this.paintTask = null; |     this.paintTask = null; | ||||||
|     this.paintedViewportMap = new WeakMap(); |     this.paintedViewportMap = new WeakMap(); | ||||||
| @ -551,7 +553,8 @@ class PDFPageView { | |||||||
|           this.renderInteractiveForms, |           this.renderInteractiveForms, | ||||||
|           this.l10n, |           this.l10n, | ||||||
|           this.enableScripting, |           this.enableScripting, | ||||||
|           /* hasJSActionsPromise = */ null |           /* hasJSActionsPromise = */ null, | ||||||
|  |           this.mouseState | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       this._renderAnnotationLayer(); |       this._renderAnnotationLayer(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user