Visualforce Winter 19 Highlights

This is quick highlights of winter 19 visualforce improvements.

1.New Visualforce Access Metrics Fields

Use the new ProfileId and LogDate access metrics fields to prioritize which Visualforce pages to migrate to Lightning Experience. To decide which Visualforce pages to migrate to Lightning Experience, it’s helpful to know which pages are used most often and by whom. These new Visualforce Access Metrics fields show you that information. The ProfileId field shows the Salesforce profile ID of the user who viewed the Visualforce page. The LogDate field shows the
date that the user accessed the page. This field provides more insight into page usage than the MetricsDate field, which represents the date the metrics are collected. To query metrics on the Visualforce pages in your org, use the VisualforceAccessMetrics object and include the ProfileId and
LogDate fields.

Select Id ,ProfileId, MetricsDate ,LogDate from VisualforceAccessMetrics

2. Securely Retrieve and Display Third-Party Images 

Protect your users from unauthorized requests by using the IMAGEPROXYURL function to securely fetch images outside your org’s server. Loading a third-party image can initiate a malicious authentication request meant to steal Salesforce usernames and passwords. This Visualforce function loads external images over HTTPS and prevents images from requesting user credentials. To securely retrieve an external image, include the IMAGEPROXYURL function on the src attribute of a tag or the value attribute of an object.

<img src={!IMAGEPROXYURL("")}/>
<apex:image value="{!IMAGEPROXYURL("")}"/>

3.URL Redirect Parameters Are No Longer Case-Sensitive

The protected URL parameters used in Visualforce pages—retURL, startURL, cancelURL, and saveURL—are no longer case-sensitive. If you change the parameter value from retURL to returl, the system now recognizes it as a protected parameter. Protected URL parameters allow redirects from Visualforce pages to or * domains and prevent malicious
redirects to third-party domains.

4.Improve Security by Isolating Untrusted Third-Party Content with iframes

You can now isolate HTML static resources on a separate domain using iframes. Using a separate domain to embed information from untrusted sources protects your Visualforce content.To reference a static HTML file on a separate domain, use $IFrameResource.<resource_name> as a merge field, where resource_name is the name you specified when you uploaded the static resource


Visualforce Page Actions


In this blog post, I am going to explain how to set up global actions and object-specific actions by using visualforce page.The global action which we are going to see in this blog is to send the message to slack channel and object specific action is to take the case ownership from the assigned queues.

Global Actions:-

As we are going to going to post the message to slack by using the global action, go and create a Slack app and get the slack token. Please refer this link

visualforce page is here below

<apex:page controller="GlobalSlackActions" lightningStylesheets="true" sidebar="false" showQuickActionVfHeader="false" docType="html-5.0">
    <apex:slds />
    <div class="slds-scope">
        <apex:form >
            <apex:actionFunction action="{!postMessagetoSlack}" name="postSlackMessage" rerender="out"
            <apex:outputPanel id="out">
                <div class="slds-form--compound slds-m-top--xxx-small">
                    <div class="slds-form-element">
                        <div class="slds-form-element__row slds-align_absolute-center">
                            <label class="slds-form-element__label" for="sample1">#Slack Channel</label>
                            <div class="slds-form-element__control slds-picklist">
                                <apex:selectList id="countries" value="{!slackChannel}" size="1" required="true" styleClass="slds-input">
                                    <apex:selectOptions value="{!ListOfSlackChannels}"/>
                    <div class="slds-form-element">
                        <div class="slds-form-element__row slds-align_absolute-center ">
                            <label class="slds-form-element__label " for="sample2"> Name </label>
                            <div class="slds-form-element__control slds-picklist">
                                <apex:inputTextArea value="{!slackTextMessge}"  id="sample1" styleClass="slds-input"/>
            <div class="slds-form-element slds-align_absolute-center">
                <button  onclick="postSlackMessage();" styleClass="slds-button slds-button_neutral">Post to Slack </button>
                <button  onclick="closeAction();" styleClass="slds-button slds-button_neutral">Close  </button>


And the controller is here.

public class GlobalSlackActions {
    public String slackChannel {get;set;}
    public List<SelectOption> channelNameOptions{get;set;}
    public String slackTextMessge {get;set;}
    public String slackToken {get;set;}
    public GlobalSlackActions(){
        slackToken ='xoxp-167092741267-167199659508-179751455458-6fd12a391bd432b735b4efcb1313193f';
    public List<SelectOption> getListOfSlackChannels () {
        String slackEndpoint = ''+slackToken;
        HttpResponse res = buildHttp(slackEndpoint);
        if(res.getStatusCode() ==200){
            JSON2Apex cls =  (JSON2Apex)parse(res.getBody());
            list<Channels> c =cls.channels ; 
            List<SelectOption> options = new List<SelectOption>();
            for(Channels cName :c){
                options.add(new SelectOption(,;
            return options ; 
            return null ;
    public class JSON2Apex {
        public Boolean ok;
        public List<Channels> channels{get;set;}
    public class Channels {
        public String id;
        public String name{get;set;}
        public Boolean is_channel;
        public Integer created;
        public String creator;
        public Boolean is_archived;
        public Boolean is_general;
        public Integer unlinked;
        public String name_normalized;
        public Boolean is_shared;
        public Boolean is_org_shared;
        public Boolean is_member;
        public Boolean is_private;
        public Boolean is_mpim;
        public List<String> members;
        public Integer num_members;
    public static JSON2Apex parse(String json) {
        return (JSON2Apex) System.JSON.deserialize(json, JSON2Apex.class);
    public PageReference postMessagetoSlack () {
        String slackEndpoint = ''+slackToken+'&channel='+slackChannel+'&text='+slackTextMessge+'&pretty=1';
        return null ;
    public HttpResponse buildHttp(String url){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse(); 
        res = h.send(req);
        return res ;

Go to set up, find global actions and create a new one as shown below.

Add to the publisher layout as shown below.

After saving the publishers layout you can able to see the quick actions as shown below.

Object Specific actions

Now I am going to walk thru how to create the object specific action.To associate an action to object you need to user standardController attribute of the object. Here we are going to create a Case Quick actions which are allowing the user to take the ownership of the case from case queue.

Here is the visual force page

<apex:page standardController="Case" extensions="CaseTakeOwnership" showQuickActionVfHeader="false">
    <apex:form id="demo">
        <apex:actionFunction action="{!takeOwnership}" name="takeOwner" reRender="demo" oncomplete="refreshFeed();"/>
        <div style="align:center">
            <button  onclick="takeOwner();" class="sucessBtn">Take Ownership</button> <br/><br/><br/><br/>
            <button onclick="closeAction();" class="closebtn">Cancel</button>

The controller is here below.

public class CaseTakeOwnership {
    public Case careRecord {get; set;}
    public CaseTakeOwnership(ApexPages.StandardController controller) {
        careRecord =(Case)controller.getRecord();
    public PageReference takeOwnership() {
        try {
            careRecord.OwnerId = UserInfo.getUserId();
            update careRecord;
        } catch(Exception ex){
        return null ;

Both in global action and Object actions, I set showQuickActionVfHeader to false. So that we will not able to see the Standard Submit and Cancel Global action header.

Now go to Case Object buttons and links and create a new action as shown below

Add the object to quick action, salesforce 1 and lightning action on page layout based on whether you wanted to show on salesforce class or salesforce 1 as shown below.


Publisher Events

Refresh feed (desktop) 

Sfdc.canvas.publisher.publish({ name: 'publisher.refresh',
 payload: {feed:true}

Submit hook (Salesforce1)

Sfdc.canvas.publisher.subscribe({name: "publisher.showPanel",
 onData:function(e) {
 "publisher.setValidForSubmit", payload:"true"});
Sfdc.canvas.publisher.subscribe({ name: "",
 onData: function(e) {
 alert("call some remote action here");
 Sfdc.canvas.publisher.publish({ name: "publisher.close",
 payload:{ refresh:"true" }});

Close publisher event

        Sfdc.canvas.publisher.publish({name: "publisher.close", payload:{ refresh:"true" , successMessage: 'Cancel!'}});  

Activate publish button 

    name: "publisher.setValidForSubmit", 
Clear Panel State
             { name : "publisher.clearPanelState", onData:function(e) { alert('Fire ClearPanelState'); }}

Show Panel State 

             { name : "publisher.showPanel", onData:function(e) { alert('Fire ShowPanel'); }}
Success State 
            Sfdc.canvas.publisher.publish({ name : "publisher.success", payload : { feed:true }});


Redirect (Salesforce1),view), type), parentRecordId), listViewName, scope), recordTypeId)