1 /*
   2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.*;
  28 import java.util.*;
  29 import java.net.*;
  30 import java.io.*;
  31 import javax.swing.*;
  32 import javax.swing.text.*;
  33 import javax.swing.event.*;
  34 
  35 import sun.swing.text.html.FrameEditorPaneTag;
  36 
  37 /**
  38  * Implements a FrameView, intended to support the HTML
  39  * <FRAME> tag.  Supports the frameborder, scrolling,
  40  * marginwidth and marginheight attributes.
  41  *
  42  * @author    Sunita Mani
  43  */
  44 
  45 class FrameView extends ComponentView implements HyperlinkListener {
  46 
  47 
  48     JEditorPane htmlPane;
  49     JScrollPane scroller;
  50     boolean editable;
  51     float width;
  52     float height;
  53     URL src;
  54     /** Set to true when the component has been created. */
  55     private boolean createdComponent;
  56 
  57     /**
  58      * Creates a new Frame.
  59      *
  60      * @param elem the element to represent.
  61      */
  62     public FrameView(Element elem) {
  63         super(elem);
  64     }
  65 
  66     protected Component createComponent() {
  67 
  68         Element elem = getElement();
  69         AttributeSet attributes = elem.getAttributes();
  70         String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
  71 
  72         if ((srcAtt != null) && (!srcAtt.equals(""))) {
  73             try {
  74                 URL base = ((HTMLDocument)elem.getDocument()).getBase();
  75                 src = new URL(base, srcAtt);
  76                 htmlPane = new FrameEditorPane();
  77                 htmlPane.addHyperlinkListener(this);
  78                 JEditorPane host = getHostPane();
  79                 boolean isAutoFormSubmission = true;
  80                 if (host != null) {
  81                     htmlPane.setEditable(host.isEditable());
  82                     String charset = (String) host.getClientProperty("charset");
  83                     if (charset != null) {
  84                         htmlPane.putClientProperty("charset", charset);
  85                     }
  86                     HTMLEditorKit hostKit = (HTMLEditorKit)host.getEditorKit();
  87                     if (hostKit != null) {
  88                         isAutoFormSubmission = hostKit.isAutoFormSubmission();
  89                     }
  90                 }
  91                 htmlPane.setPage(src);
  92                 HTMLEditorKit kit = (HTMLEditorKit)htmlPane.getEditorKit();
  93                 if (kit != null) {
  94                     kit.setAutoFormSubmission(isAutoFormSubmission);
  95                 }
  96 
  97                 Document doc = htmlPane.getDocument();
  98                 if (doc instanceof HTMLDocument) {
  99                     ((HTMLDocument)doc).setFrameDocumentState(true);
 100                 }
 101                 setMargin();
 102                 createScrollPane();
 103                 setBorder();
 104             } catch (MalformedURLException e) {
 105                 e.printStackTrace();
 106             } catch (IOException e1) {
 107                 e1.printStackTrace();
 108             }
 109         }
 110         createdComponent = true;
 111         return scroller;
 112     }
 113 
 114     JEditorPane getHostPane() {
 115         Container c = getContainer();
 116         while ((c != null) && ! (c instanceof JEditorPane)) {
 117             c = c.getParent();
 118         }
 119         return (JEditorPane) c;
 120     }
 121 
 122 
 123     /**
 124      * Sets the parent view for the FrameView.
 125      * Also determines if the FrameView should be editable
 126      * or not based on whether the JTextComponent that
 127      * contains it is editable.
 128      *
 129      * @param parent View
 130      */
 131     public void setParent(View parent) {
 132         if (parent != null) {
 133             JTextComponent t = (JTextComponent)parent.getContainer();
 134             editable = t.isEditable();
 135         }
 136         super.setParent(parent);
 137     }
 138 
 139 
 140     /**
 141      * Also determines if the FrameView should be editable
 142      * or not based on whether the JTextComponent that
 143      * contains it is editable. And then proceeds to call
 144      * the superclass to do the paint().
 145      *
 146      * @param parent View
 147      * @see text.ComponentView#paint
 148      */
 149     public void paint(Graphics g, Shape allocation) {
 150 
 151         Container host = getContainer();
 152         if (host != null && htmlPane != null &&
 153             htmlPane.isEditable() != ((JTextComponent)host).isEditable()) {
 154             editable = ((JTextComponent)host).isEditable();
 155             htmlPane.setEditable(editable);
 156         }
 157         super.paint(g, allocation);
 158     }
 159 
 160 
 161     /**
 162      * If the marginwidth or marginheight attributes have been specified,
 163      * then the JEditorPane's margin's are set to the new values.
 164      */
 165     private void setMargin() {
 166         int margin = 0;
 167         Insets in = htmlPane.getMargin();
 168         Insets newInsets;
 169         boolean modified = false;
 170         AttributeSet attributes = getElement().getAttributes();
 171         String marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINWIDTH);
 172         if ( in != null) {
 173             newInsets = new Insets(in.top, in.left, in.right, in.bottom);
 174         } else {
 175             newInsets = new Insets(0,0,0,0);
 176         }
 177         if (marginStr != null) {
 178             margin = Integer.parseInt(marginStr);
 179             if (margin > 0) {
 180                 newInsets.left = margin;
 181                 newInsets.right = margin;
 182                 modified = true;
 183             }
 184         }
 185         marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINHEIGHT);
 186         if (marginStr != null) {
 187             margin = Integer.parseInt(marginStr);
 188             if (margin > 0) {
 189                 newInsets.top = margin;
 190                 newInsets.bottom = margin;
 191                 modified = true;
 192             }
 193         }
 194         if (modified) {
 195             htmlPane.setMargin(newInsets);
 196         }
 197     }
 198 
 199     /**
 200      * If the frameborder attribute has been specified, either in the frame,
 201      * or by the frames enclosing frameset, the JScrollPane's setBorder()
 202      * method is invoked to achieve the desired look.
 203      */
 204     private void setBorder() {
 205 
 206         AttributeSet attributes = getElement().getAttributes();
 207         String frameBorder = (String)attributes.getAttribute(HTML.Attribute.FRAMEBORDER);
 208         if ((frameBorder != null) &&
 209             (frameBorder.equals("no") || frameBorder.equals("0"))) {
 210             // make invisible borders.
 211             scroller.setBorder(null);
 212         }
 213     }
 214 
 215 
 216     /**
 217      * This method creates the JScrollPane.  The scrollbar policy is determined by
 218      * the scrolling attribute.  If not defined, the default is "auto" which
 219      * maps to the scrollbar's being displayed as needed.
 220      */
 221     private void createScrollPane() {
 222         AttributeSet attributes = getElement().getAttributes();
 223         String scrolling = (String)attributes.getAttribute(HTML.Attribute.SCROLLING);
 224         if (scrolling == null) {
 225             scrolling = "auto";
 226         }
 227 
 228         if (!scrolling.equals("no")) {
 229             if (scrolling.equals("yes")) {
 230                 scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
 231                                            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
 232             } else {
 233                 // scrollbars will be displayed if needed
 234                 //
 235                 scroller = new JScrollPane();
 236             }
 237         } else {
 238             scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER,
 239                                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 240         }
 241 
 242         JViewport vp = scroller.getViewport();
 243         vp.add(htmlPane);
 244         vp.setBackingStoreEnabled(true);
 245         scroller.setMinimumSize(new Dimension(5,5));
 246         scroller.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
 247     }
 248 
 249 
 250     /**
 251      * Finds the outermost FrameSetView.  It then
 252      * returns that FrameSetView's container.
 253      */
 254     JEditorPane getOutermostJEditorPane() {
 255 
 256         View parent = getParent();
 257         FrameSetView frameSetView = null;
 258         while (parent != null) {
 259             if (parent instanceof FrameSetView) {
 260                 frameSetView = (FrameSetView)parent;
 261             }
 262             parent = parent.getParent();
 263         }
 264         if (frameSetView != null) {
 265             return (JEditorPane)frameSetView.getContainer();
 266         }
 267         return null;
 268     }
 269 
 270 
 271     /**
 272      * Returns true if this frame is contained within
 273      * a nested frameset.
 274      */
 275     private boolean inNestedFrameSet() {
 276         FrameSetView parent = (FrameSetView)getParent();
 277         return (parent.getParent() instanceof FrameSetView);
 278     }
 279 
 280 
 281     /**
 282      * Notification of a change relative to a
 283      * hyperlink. This method searches for the outermost
 284      * JEditorPane, and then fires an HTMLFrameHyperlinkEvent
 285      * to that frame.  In addition, if the target is _parent,
 286      * and there is not nested framesets then the target is
 287      * reset to _top.  If the target is _top, in addition to
 288      * firing the event to the outermost JEditorPane, this
 289      * method also invokes the setPage() method and explicitly
 290      * replaces the current document with the destination url.
 291      *
 292      * @param HyperlinkEvent
 293      */
 294     public void hyperlinkUpdate(HyperlinkEvent evt) {
 295 
 296         JEditorPane c = getOutermostJEditorPane();
 297         if (c == null) {
 298             return;
 299         }
 300 
 301         if (!(evt instanceof HTMLFrameHyperlinkEvent)) {
 302             c.fireHyperlinkUpdate(evt);
 303             return;
 304         }
 305 
 306         HTMLFrameHyperlinkEvent e = (HTMLFrameHyperlinkEvent)evt;
 307 
 308         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
 309             String target = e.getTarget();
 310             String postTarget = target;
 311 
 312             if (target.equals("_parent") && !inNestedFrameSet()){
 313                 target = "_top";
 314             }
 315 
 316             if (evt instanceof FormSubmitEvent) {
 317                 HTMLEditorKit kit = (HTMLEditorKit)c.getEditorKit();
 318                 if (kit != null && kit.isAutoFormSubmission()) {
 319                     if (target.equals("_top")) {
 320                         try {
 321                             movePostData(c, postTarget);
 322                             c.setPage(e.getURL());
 323                         } catch (IOException ex) {
 324                             // Need a way to handle exceptions
 325                         }
 326                     } else {
 327                         HTMLDocument doc = (HTMLDocument)c.getDocument();
 328                         doc.processHTMLFrameHyperlinkEvent(e);
 329                     }
 330                 } else {
 331                     c.fireHyperlinkUpdate(evt);
 332                 }
 333                 return;
 334             }
 335 
 336             if (target.equals("_top")) {
 337                 try {
 338                     c.setPage(e.getURL());
 339                 } catch (IOException ex) {
 340                     // Need a way to handle exceptions
 341                     // ex.printStackTrace();
 342                 }
 343             }
 344             if (!c.isEditable()) {
 345                 c.fireHyperlinkUpdate(new HTMLFrameHyperlinkEvent(c,
 346                                                                   e.getEventType(),
 347                                                                   e.getURL(),
 348                                                                   e.getDescription(),
 349                                                                   getElement(),
 350                                                                   e.getInputEvent(),
 351                                                                   target));
 352             }
 353         }
 354     }
 355 
 356     /**
 357      * Gives notification from the document that attributes were changed
 358      * in a location that this view is responsible for.  Currently this view
 359      * handles changes to its SRC attribute.
 360      *
 361      * @param e the change information from the associated document
 362      * @param a the current allocation of the view
 363      * @param f the factory to use to rebuild if the view has children
 364      *
 365      */
 366     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 367 
 368         Element elem = getElement();
 369         AttributeSet attributes = elem.getAttributes();
 370 
 371         URL oldPage = src;
 372 
 373         String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
 374         URL base = ((HTMLDocument)elem.getDocument()).getBase();
 375         try {
 376             if (!createdComponent) {
 377                 return;
 378             }
 379 
 380             Object postData = movePostData(htmlPane, null);
 381             src = new URL(base, srcAtt);
 382             if (oldPage.equals(src) && (src.getRef() == null) && (postData == null)) {
 383                 return;
 384             }
 385 
 386             htmlPane.setPage(src);
 387             Document newDoc = htmlPane.getDocument();
 388             if (newDoc instanceof HTMLDocument) {
 389                 ((HTMLDocument)newDoc).setFrameDocumentState(true);
 390             }
 391         } catch (MalformedURLException e1) {
 392             // Need a way to handle exceptions
 393             //e1.printStackTrace();
 394         } catch (IOException e2) {
 395             // Need a way to handle exceptions
 396             //e2.printStackTrace();
 397         }
 398     }
 399 
 400     /**
 401      * Move POST data from temporary storage into the target document property.
 402      *
 403      * @return the POST data or null if no data found
 404      */
 405     private Object movePostData(JEditorPane targetPane, String frameName) {
 406         Object postData = null;
 407         JEditorPane p = getOutermostJEditorPane();
 408         if (p != null) {
 409             if (frameName == null) {
 410                 frameName = (String) getElement().getAttributes().getAttribute(
 411                         HTML.Attribute.NAME);
 412             }
 413             if (frameName != null) {
 414                 String propName = FormView.PostDataProperty + "." + frameName;
 415                 Document d = p.getDocument();
 416                 postData = d.getProperty(propName);
 417                 if (postData != null) {
 418                     targetPane.getDocument().putProperty(
 419                             FormView.PostDataProperty, postData);
 420                     d.putProperty(propName, null);
 421                 }
 422             }
 423         }
 424 
 425         return postData;
 426     }
 427 
 428     /**
 429      * Determines the minimum span for this view along an
 430      * axis.
 431      *
 432      * @param axis may be either <code>View.X_AXIS</code> or
 433      *  <code>View.Y_AXIS</code>
 434      * @return the preferred span; given that we do not
 435      * support resizing of frames, the minimum span returned
 436      * is the same as the preferred span
 437      *
 438      */
 439     public float getMinimumSpan(int axis) {
 440       return 5;
 441     }
 442 
 443     /**
 444      * Determines the maximum span for this view along an
 445      * axis.
 446      *
 447      * @param axis may be either <code>View.X_AXIS</code> or
 448      *  <code>View.Y_AXIS</code>
 449      * @return the preferred span; given that we do not
 450      * support resizing of frames, the maximum span returned
 451      * is the same as the preferred span
 452      *
 453      */
 454     public float getMaximumSpan(int axis) {
 455         return Integer.MAX_VALUE;
 456     }
 457 
 458     /** Editor pane rendering frame of HTML document
 459      *  It uses the same editor kits classes as outermost JEditorPane
 460      */
 461     @SuppressWarnings("serial") // Superclass is not serializable across versions
 462     class FrameEditorPane extends JEditorPane implements FrameEditorPaneTag {
 463         public EditorKit getEditorKitForContentType(String type) {
 464             EditorKit editorKit = super.getEditorKitForContentType(type);
 465             JEditorPane outerMostJEditorPane = null;
 466             if ((outerMostJEditorPane = getOutermostJEditorPane()) != null) {
 467                 EditorKit inheritedEditorKit = outerMostJEditorPane.getEditorKitForContentType(type);
 468                 if (! editorKit.getClass().equals(inheritedEditorKit.getClass())) {
 469                     editorKit = (EditorKit) inheritedEditorKit.clone();
 470                     setEditorKitForContentType(type, editorKit);
 471                 }
 472             }
 473             return editorKit;
 474         }
 475 
 476         FrameView getFrameView() {
 477             return FrameView.this;
 478         }
 479     }
 480 }